- 投稿日:2019-10-21T22:54:51+09:00
単純なコールバックを作ってみる
〇概要
Androidアプリでアーキテクチャーが複雑化してくると非同期の情報を行うクラスとそれを使うクラスが分かれてきます。
そうなると非同期で得たデータを取得したタイミングで別クラスに教えてあげなければならないわけですが結構面倒。
というわけで、サポートライブラリのAsyncTaskLoaderとかイベントバスを扱うライブラリ(EventBusやRxJavaなど)が色々あるわけですが、インターフェースを利用した原始的なコールバックの作り方の情報があまりなかったのでまとめてみました。〇サンプルソース
〇クラス一覧
MainActivity
・メインクラス
・TasksRepositoryとTasksBackgroundDataSourceのインスタンスを生成。
・シンプルにするためにViewの表示関数もここに入れてあります。TasksRepository
・データを保持して操作するクラス。
・生成時にTasksBackgroundDataSourceのインスタンスを取得しています。
・データ取得の依頼をTasksBackgroundDataSourceに投げたり、コールバックを受け取りMainActivityに表示の依頼を投げる。TasksBackgroundDataSource
・非同期でデータを持ってくるクラス。
・TasksDataSourceインターフェースをimplementsしている。
・(ネットワークからデータを持ってくる処理を書くと処理が煩雑になるので、別スレッド立ててデータ埋め込んでいるだけ)TasksDataSource
・データ取得の処理関係をまとめたインターフェース。
Task
・取得した値を管理するデータクラス。
〇コールバック部分抜粋
(注)理解しやすいために上のクラス一覧とは逆順に記載
データ取得の関数とコールバックとして呼ばれる関数を定義
TasksDataSourcepublic interface TasksDataSource { // コールバックとして呼ばれる関数 interface LoadTasksCallback { void onTasksLoaded(List<Task> tasks); void onDataNotAvailable(); } // データ取得関数(別に書かなくてよいがまとめて書いておく) void getTasks(@NonNull LoadTasksCallback callback); }データ取得関数getTasksを実装
取得後はコールバック関数を呼んであげるTasksBackgroundDataSourcepublic class TasksBackgroundDataSource implements TasksDataSource { @Override public void getTasks(@NonNull final LoadTasksCallback callback) { final Handler handler = new Handler(); // スレッド立ち上げ Thread thread = new Thread(new Runnable() { @Override public void run() { // Handlerを使用してメインスレッドに処理を依頼する(View更新のため) handler.post(new Runnable() { @Override public void run() { // データ取得 // ・ // ・ if (tasks.isEmpty()) { // データがない時の処理 callback.onDataNotAvailable(); } else { // データがあった時の処理 callback.onTasksLoaded(tasks); } } }); } }); thread.start(); } }TasksBackgroundDataSourceのgetTaskを呼び出して引数にLoadTasksCallbackのインスタンスを生成して呼び出してほしい関数を実装して渡す。
TasksRepositorymTasksBackgroundSource.getTasks(new TasksDataSource.LoadTasksCallback() { // データ取得後に呼ばれる処理(データありの時) @Override public void onTasksLoaded(List<Task> tasks) { MainActivity.showTextMsg(changeTasksToString(tasks)); } // データ取得後に呼ばれる処理(データなしの時) @Override public void onDataNotAvailable() { Log.w("DEBUG_DATA","TaskRepository onDataNotAvailable"); } });〇まとめ
結局はこれ呼んどいてね!ってデータ取得する関数に処理を実装したインスタンスを投げてるだけですが、それを実現するための設計が複雑ですね。
これはGoogleのMVPサンプルを元につくったのですが、そこでは
PresenterがRepositoryのgetTaskを呼び、そうするとDataSourceのgetTaskが呼ばれてと、数珠つなぎでcallbackが呼ばれていくという仕組みでした。
取得先も増えてくるとわけわからなくなってくるので、やっぱりライブラリを使いましょうということですね。
とりあえず基本をまとめてみました。〇参考
- 投稿日:2019-10-21T22:00:37+09:00
[Android] コピーできないテキストをコピーする
Androidで選択できない(textIsSelectable=false)テキストをコピーする方法。
スクショだと取り回しづらいし長文だと地獄なので、見つけた方法をメモしておく。結論から言えば、
uiautomator dump
コマンドで表示しているテキストの全文を取得できる。
たとえ選択できる場合でも、PCでゴニョゴニョしたい場合は、「選択」→「共有」より便利。
確認はAndroid10で実施した。この状態で
uiautomator dump
コマンドを実施した結果が以下(見やすいようにxmllint
で整形している)。
resource-id="xyz.takeoverjp.jugemuviewer:id/jugemView"
のtext
の中身が、目的の文字列になっていることがわかる。$ adb shell uiautomator dump UI hierchary dumped to: /sdcard/window_dump.xml $ adb shell cat /sdcard/window_dump.xml | xmllint --format - <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <hierarchy rotation="0"> <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2040]"> <node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2040]"> <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,60][1080,2040]"> <node index="0" text="" resource-id="xyz.takeoverjp.jugemuviewer:id/decor_content_parent" class="android.view.ViewGroup" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,60][1080,2040]"> <node index="0" text="" resource-id="xyz.takeoverjp.jugemuviewer:id/action_bar_container" class="android.widget.FrameLayout" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,60][1080,200]"> <node index="0" text="" resource-id="xyz.takeoverjp.jugemuviewer:id/action_bar" class="android.view.ViewGroup" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,60][1080,200]"> <node index="0" text="JugemuViewer" resource-id="" class="android.widget.TextView" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[40,96][377,163]"/> </node> </node> <node index="1" text="" resource-id="android:id/content" class="android.widget.FrameLayout" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,200][1080,2040]"> <node index="0" text="" resource-id="" class="android.view.ViewGroup" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,200][1080,2040]"> <node index="0" text="" resource-id="" class="android.widget.ScrollView" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="true" long-clickable="false" password="false" selected="false" bounds="[29,200][1052,2040]"> <node index="0" text="寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助寿限無、寿限無、 五劫の擦り切れ、 海砂利水魚の、 水行末・雲来末・風来末、 喰う寝る処に住む処、 藪ら柑子の藪柑子、 パイポ・パイポ・パイポのシューリンガン、 シューリンガンのグーリンダイ、 グーリンダイのポンポコピーのポンポコナーの、 長久命の長助" resource-id="xyz.takeoverjp.jugemuviewer:id/jugemView" class="android.widget.TextView" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[29,200][1052,2040]"/> </node> </node> </node> </node> </node> </node> <node index="1" text="" resource-id="android:id/statusBarBackground" class="android.view.View" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,60]"/> <node index="2" text="" resource-id="android:id/navigationBarBackground" class="android.view.View" package="xyz.takeoverjp.jugemuviewer" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][0,0]"/> </node> </hierarchy>
uiautomator
コマンドはUIを含む自動テスト用のフレームワークの一部。
テスト作成時のレイアウト調査用にdump機能を持っているので、利用した。なお、Android Studioに付属するLayout Inspectorや、Android SDKに付属する
uiautomatorviewer
を使っても同じことができる。
GUIが好きな場合や、nodeと表示との関係がよくわからない場合はこれらを使えばいい。
TextView
のmText
を選択してCtrl+C
すればコピー可能。
- Layout Inspector
uiautomatorviewer
参考
Test UI for multiple apps - Android Developer
Layout Inspector - Android Developer
TextView#textIsSelectable - Android Developer
UiAutomatorについて - Qiita
寿限無 - Wiipedia
- 投稿日:2019-10-21T16:22:21+09:00
MacターミナルからAPKファイルをAndroidの実機端末にインストール
前提条件
Android Studioが入っていること
手順
- bash_profileにSDKのパスを通す
- インストールを行う。
bash_profileにSDKのパスを通す
ターミナルを開き以下のコマンドを入力します。
vim ~/.bash_profile先ほど確認したAndroid SDKのパスを記入します
export PATH=$PATH:/Users/ユーザ名/Library/Android/sdk/platform-toolsAndroid Studioを使用しているので同環境の方はSDKのパスのユーザ名のみの変更で問題ないかと思います。
※SDKのパスはAndroid Studioで「File」>「Other Settings」>「Default Project Structure…」ここに記載されています。
bash_profileを再度読み込む
方法は二つあります。
1.ターミナルを再起動2.コマンドで改めて読み込む
source ~/.bash_profileadbコマンドを利用することができます。
インストール
adb install xxx.apkxxx.apkはapkファイルまでのパスを入れてください。
Successが表示されたらインストール完了です。
Android Studioからrunするのとは違って自動ではアプリは開かれないです。補足
- adbとは『Android Debug Bridge』の略でAndroidのデバッグをサポートするツールです。 adbはAndroid SDKに含まれていますので、開発マシンにパスを通せば使えるようになります。
- androidとUSB接続時にデバックモードないと反映されません。
- 投稿日:2019-10-21T15:12:09+09:00
Flutterウィークリー #80
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#80の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-80※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
フレアとFlutter使用したソロノードのアニメーション化
https://medium.com/@mandylowry/animating-solo-nodes-with-flare-and-flutter-20e92c54cacd
She Who Codesによるこのチュートリアルで、単一のFlareアニメーションでさまざまなアクセサリーを切り替える方法を学びます。WordPressバックエンドでFlutterアプリを構築する-パート1
Ritesh Sharmaが、WordpressをFlutterアプリのバックエンドとして使用する方法を説明します。Gitpod + Flutter =モバイルアプリ開発者向けの外出先での生産性
GitpodとオンラインIDEを使用してFlutterプロジェクトを作成する方法。FlutterプロジェクトをREST APIとしてホストする
https://medium.com/@rody.davis.jr/host-your-flutter-project-as-a-rest-api-751b8301f71f
Rody Davisは、クライアントFlutterアプリとDart APIサーバーでモデルとビジネスロジックを再利用する方法について説明しています。FlutterネイティブのサードパーティSDKを統合する
https://medium.com/@jessanirahim/integrating-native-third-party-sdk-in-flutter-8aab03afa9da
FlutterアプリでネイティブのサードパーティSDKを使用する必要がありますか? Rahim Khalidが記事を書いてくれました。最高のFlutterレスポンシブUIパターン
https://medium.com/flutter-community/the-best-flutter-responsive-ui-pattern-ba52875d70cd
Dane Mackierは、アプリをレスポンシブにするために必要なすべての情報を取得して使用する方法について説明します。Flutterアプリのアニメーション化
https://www.smashingmagazine.com/2019/10/animation-apps-flutter/
Shubhamは、 Flutterアニメーションのこのすばらしい詳細な紹介を書きました。ビデオ&メディア
Dart & Flutterタイプを使用した設計-合計タイプチュートリアル
https://www.youtube.com/watch?v=Pjx_Q5styqs
Dart Sum Typesを使用して、コードのエラーを起こしやすくする方法を学ぶFlutter + TensorFlow Lite |オブジェクト検出| YoloV2 | SSDチュートリアル
https://www.youtube.com/watch?v=0pYh7Js4GM8
TFLiteをTiny Yolov2およびSSDモデルで使用して、デバイス上のオブジェクト検出を実行する方法Flutter Provider:はじめに
https://www.youtube.com/watch?v=O71rYKcxUgA
Flutter Providerパッケージを使用する方法を学びます。フラッターのテキスト読み上げ(TTS)
https://www.youtube.com/watch?v=WnJZOi57oTY
Text To Speechについて、このビデオを使ってアプリに話しかけますライブラリ&コード
アントニオット15 / fgf
https://github.com/antoniott15/fgf
flutterでのGoogleフォントのインストール用CLI
bratan / flutter_translate
https://github.com/bratan/flutter_translate
Flutterの国際化(i18n)ライブラリ。
feilfeilundfeil / flutter_in_app_update
https://github.com/feilfeilundfeil/flutter_in_app_update
公式のAndroid APIを使用して、Androidのアプリ内アップデートを有効にします。
snowballdigital / flutter-unity-view-widget
https://github.com/snowballdigital/flutter-unity-view-widget
Flutter埋め込み可能な単一ゲームエンジンビュー
Vardiak /アニメーション
https://github.com/Vardiak/Animated
Flutter用の非常にシンプルな(まだ強力な)暗黙的なアニメーションライブラリ。
- 投稿日:2019-10-21T14:40:53+09:00
AppiumでAndroid端末にインストールされたあらゆるアプリの操作を自動化してみよう
Appiumで端末にインストールされたアプリの操作を自動化しクローリングしたいと思います。
AppiumはSeleniumのようなAPIでアプリの操作をできるツールです。
端末に入っているapkをpullしてAppiumで操作するのでどんなアプリでも操作ができます。
Appiumをインストール
Appium公式サイトからアプリをダウンロードします。
http://appium.io/端末に接続する方法
下記からは端末に接続する方法を書いていきます。
下記に公式のドキュメントがありますので、うまくいかない場合はチェックしてみてください。
https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md下記の項目を取得することでAppiumのSessionを開始することができます。
appPackage appActivity platformVersion deviceName app(任意)Packageの取得
その後、PCにスマートフォンをつなぎ(自分の場合はAndroidです)、下記のコマンドを実行
adb shell pm list packagesパッケージ一覧を取得できますので、そこから操作したいアプリのパッケージ名をメモしておきます。(
package:
は不要)Launch Activityの取得
操作したいアプリを立ち上げ、下記のコマンドを実行することでLaunch Activityを取得することができます。
adb shell dumpsys window windows | grep -E "mCurrentFocus"メモしておいてください。
deviceNameの取得
adb shell getprop ro.product.modelこれもメモしておきます。
platformVersionの取得
adb shell getprop ro.build.version.releaseこれもメモ
appの取得(任意)
ここではapkファイルを端末からpullして、PCのapkへのパスを取得します。
まず、端末内のアプリのパスを取得します。
adb shell pm list packages -f | grep com.hogeその後、上記で取得できたパスを使ってapkファイルをダウンロードします
adb pull /data/app/com.hoge-2NCYKBV5gI-ISQMHfmhcoA==/base.apkダウンロードしたapkのPCでのパスをメモしておいてください。
これまでにメモしておいた内容で下記のJSONを作成します。
{ "automationName": "Appium", "platformName": "Android", "appPackage": "上記で取得した値", "appActivity": "上記で取得した値", "platformVersion": "上記で取得した値", "deviceName": "上記で取得した値", "app": "上記で取得した値(任意)" }
JSONを読み込ませてアプリを起動
JSON Representationの部分に上記で作成したJSONを設定してStart Session をすることで
端末からpullしたapkを端末に送り込み、インスペクタが起動します。上記のインスペクタを使えばxpathが取得できるので、あとはそれをつかってseleniumを書くようにスクリプトを書いたり、レコーディングの機能を使って端末の操作をコード化することができます。
コードは自分はWebdriver.ioで記述しますが、Ruby,Java,PythonなどでもOKです。
ここはSeleniumの話になってしまうので割愛します。
- 投稿日:2019-10-21T14:14:44+09:00
android adb
ADB是什么
ADB,即 Android Debug Bridge 是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具,它可为各种设备操作提供便利,如安装和调试应用,并提供对
Unix shell
(可用来在模拟器或连接的设备上运行各种命令)的访问。可以在Android SDK/platform-tools
中找到adb
工具或下载 ADB Kits 。查看adb的版本信息
adb version
显示设备列表
adb devices List of devices attached LK7N18125001132 device关闭usb调试
adb shell settings put global adb_enabled 0
打开usb调试
adb shell settings put global adb_enabled 1
查询usb调试开关情况
adb shell settings get global adb_enabled
打开模拟定位
adb shell settings put secure mock_location 1
打开坐标
adb shell settings put system pointer_location 1
模拟home按键
adb shell input keyevent 3
模拟按下 Power 键
adb shell input keyevent 26
模拟 touch屏幕事件
点击屏幕(x, y) = (350, 350)位置
adb shell input tap 350 350
模拟滑动的事件
从屏幕(150, 150) 滑到屏幕(300, 300)
adb shell input swipe 150 150 300 300
输入文字
adb shell input text 123
查看 WiFi IP 地址
adb shell ip -f inet addr show wlan0 <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 3000 inet 192.168.0.100/24 brd 192.168.0.255 scope global wlan0 valid_lft forever preferred_lft forever开启adb服务
默认启动
5037
端口adb start-server netstat -na | grep 5037 tcp4 0 0 127.0.0.1.5037 127.0.0.1.65208 ESTABLISHED tcp4 0 0 127.0.0.1.65208 127.0.0.1.5037 ESTABLISHED tcp4 0 0 127.0.0.1.5037 127.0.0.1.65206 ESTABLISHED tcp4 0 0 127.0.0.1.65206 127.0.0.1.5037 ESTABLISHED tcp4 0 0 127.0.0.1.5037 127.0.0.1.65204 ESTABLISHED tcp4 0 0 127.0.0.1.65204 127.0.0.1.5037 ESTABLISHED tcp4 0 0 127.0.0.1.5037 127.0.0.1.65202 ESTABLISHED tcp4 0 0 127.0.0.1.65202 127.0.0.1.5037 ESTABLISHED tcp4 0 0 127.0.0.1.5037 *.* LISTEN查看系统应用
adb shell pm list packages -s
查看第三方应用
adb shell pm list packages -3
查看应用安装路径
adb shell pm path <package-name>
通过 TCP 连接设备
adb tcpip 5555 adb connect 192.168.1.3:5555 adb devices关闭 adb服务
adb kill-server
参考:
https://juejin.im/post/5b5683bcf265da0f9b4dea96
https://developer.android.com/studio/command-line/adb?hl=zh-Cn
https://cwgoover.github.io/2016/05/31/android-setting-pointer-location/
https://android.stackexchange.com/a/114081
- 投稿日:2019-10-21T09:38:24+09:00
Scala + libGDXで、Androidアプリをリリースしてみた
AndroidもScalaで書きたい!という衝動に身を任せ、パズルゲームを作ってみました。
公開中のゲームはこちらです。4方向にフィールドのあるパズルゲームです。ブロックを積んで消していく風のやつですね。
コア部分のコードはこちらです。これまで、ブラウザで動くscala-js版や、ターミナル上で動くscala-native版を作っていたのですが、満を持してAndroid版に挑戦した次第です。
今回は、とりあえずリリースまで出来たということで、セットアップやScalaで書く上での実装方針など、軽く触れたいと思います。
モチベーション
- Scalaの資産を活用してAndroidアプリを作りたい
- Androidアプリをリリースして、知見を得たり、実験をしたい
前者は完全な趣味の領域です。Scalaでなんでも書きたい欲を満たしたいだけです。
後者は、お仕事でAndroidを触る中で、色々と試してみたいことがあるけれど、さすがにお仕事のプロジェクトで実験するわけにもいかず、ならば自分でアプリをリリースしてまえ、というノリです。
今回、初めて自分のアプリをリリースしましたが、イチから自分でやってみるとやはり違いますね。今まで曖昧だった知識がスッと理解できたりします。libGDXについて
libGDXは、オープンソースのクロスプラットフォームのゲーム開発フレームワークです。Javaで書かれているので、JVM言語でJavaとの相互運用が可能あれば、任意の言語で書くことができます。
デスクトップ/Android/iOS/HTML5に対応していますが、HTML5で動かすにはGWT経由となり、Javaで書く必要があります。また、iOS向けにはMOSを使うらしいのですが、試したことがないので詳細は分かりません...。※2019.10.22追記 - iOS向けにはRoboVMの選択肢もあるということをコメントで教えていただきました。
非公式ですが、ドキュメントの翻訳も公開されています。
セットアップ
libGDXでは、専用のセットアップツールが用意されていて、簡単にプロジェクトの雛形を作ってくれます。
詳細は公式のドキュメントを見ていただければと思います。
Sub Projects
では生成するプロジェクトを選択できます。今回はAndroid版を作りたかったのと、検証用にデスクトップ版を生成しました。ビルドツールはgradleです。
ここにScala用のプラグインを入れることでScalaのビルドに対応しました。また、使用するScalaのバージョンは2.11にしています。2.12でビルドすると、実行時に互換がなく落ちてしまいました。Scala 2.12はJava8を要求しますが、Android側の互換がないということかと思います。
詳しくはドキュメントも参照してみてください。
sbt経由のアプローチもあるにはありますが、プラグインがメンテナンスされていないようで不安があり、採用しませんでした。コーディングについて
コードはScalaで書けますが、もちろんJavaや、その他のJVM言語でも書けます。
Scalaは2.11なので、ちょこちょこと不便なところが出て来ますが、今回のベースとなったコードが元々2.11向けだった(scala-nativeサポートのため) ので、その点は楽でした。
libGDXでは、マルチプラットフォーム間で共有するcoreプロジェクトと、それぞれのプラットフォーム向けのプロジェクトがあります。
基本となるアプリケーションコードはほぼcoreに書けますが、プラットフォーム固有の処理はそれぞれのプロジェクト側に書きます。
Android固有の特殊なことがしたい場合はココをいじることになります。libGDXは何をしているのか?
libGDXの主たる責務は、プラットフォーム間の差異を抽象化して共通のインターフェースを開発者に提供しすることです。
描画、イベントハンドリング、オーディオ、ネットワークアクセスなどをカバーしています。描画に関しては、OpenGLをベースとしています。開発者は基本的にはテクスチャを座標指定で描画し、それをlibGDXがOpenGLを介してレンダリングします。
Androidにおいては、これをAndroidGraphicsクラス経由で行っています。このクラスはViewを提供するので、このViewをレイアウトすることで、Androidアプリとしての描画が実現されています。
このドキュメント は広告を埋め込む話ですが、この辺りに触れています。つまるところ、ただのViewなので、ActivityやFragmentと組み合わせて使うことができます。
逆に、libGDXだけで完結する場合、ActivityやFragmentを意識することはほぼありません。よしなにハイブリットにすることで、互いの弱点を補ったアプリを作ることも簡単です。
あとがき
Scalaで書いたAndroidアプリのリリースまで漕ぎ着けたので、簡単にご紹介しました。
Scalaで書ける、というのはやはり強みです。
願わくば2.11の縛りを突破したいですが・・・。アプリとしては、まだまだ不完全ですが、今後、色々と実験がてら拡張していこうと思います。
- 投稿日:2019-10-21T09:36:04+09:00
recyclerview-animatorsを使ってみて
はじめに
現在開発しているAndroidアプリでRecyclerViewを使用しており、
そこにアニメーションをつけるときに発見しました。recyclerview-animatorsとは
recyclerview-animatorsは、RecyclerViewのitemにアニメーションをつけるものです。
これを使うことにより、RecyclerViewにitemを追加や削除したときに、様々なアニメーションをつけることができます。使用方法
1.build.gradleにrecyclerview-animatorsのimplementationを追加
2.使用したいアニメーションをRecyclerViewにセット
3.RecyclerViewにitem追加と削除の処理を書くの3ステップで使用できます。
1. build.gradleにrecyclerview-animatorsのimplementationを追加
まず、build.gradleに
build.gradledependencies { implementation 'jp.wasabeef:recyclerview-animators:3.0.0' }を追加します。
2. 使用したいアニメーションをRecyclerViewにセット
build.gradleに追加が終わったら、RecyclerViewのitemAnimatorに動作させたいアニメーションを記述します。
MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { recyclerView.itemAnimator = SlideInLeftAnimator() // 左からスライドAnimationが実行される }3. RecyclerViewにitem追加と削除の処理を書く
最後に、RecyclerViewにitemを追加と削除するときの処理を記述します。
今回はGroupieを使用しているため、以下のように書きました。MainActivity.ktprivate val groupAdapter = GroupAdapter<ViewHolder>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView.apply { adapter = groupAdapter itemAnimator = SlideInLeftAnimator(DecelerateInterpolator(0.1f)) layoutManager = LinearLayoutManager(context) } create.setOnClickListener { groupAdapter.add(Item(groupAdapter.itemCount.toString())) groupAdapter.notifyItemChanged(groupAdapter.itemCount) } delete.setOnClickListener { if (groupAdapter.itemCount < 1) { return@setOnClickListener } groupAdapter.removeGroup(groupAdapter.itemCount - 1) groupAdapter.notifyItemChanged(groupAdapter.itemCount - 1) } }このように書くことでアニメーションが動作します!
スライドイン スライドアウト 他にもフェードイン・アウトアニメーションやフリップアニメーション、
また動作方向やアニメーション変化を変更することができるみたいです。まとめ
アニメーションをつけることは本来難しいのですが、とても簡単に実装できるため、
これから使っていこうと思いました。