20191021のAndroidに関する記事は8件です。

単純なコールバックを作ってみる

〇概要

Androidアプリでアーキテクチャーが複雑化してくると非同期の情報を行うクラスとそれを使うクラスが分かれてきます。
そうなると非同期で得たデータを取得したタイミングで別クラスに教えてあげなければならないわけですが結構面倒。
というわけで、サポートライブラリのAsyncTaskLoaderとかイベントバスを扱うライブラリ(EventBusやRxJavaなど)が色々あるわけですが、インターフェースを利用した原始的なコールバックの作り方の情報があまりなかったのでまとめてみました。

〇サンプルソース

GitHub

〇クラス一覧

MainActivity

・メインクラス
・TasksRepositoryとTasksBackgroundDataSourceのインスタンスを生成。
・シンプルにするためにViewの表示関数もここに入れてあります。

TasksRepository

・データを保持して操作するクラス。
・生成時にTasksBackgroundDataSourceのインスタンスを取得しています。
・データ取得の依頼をTasksBackgroundDataSourceに投げたり、コールバックを受け取りMainActivityに表示の依頼を投げる。

TasksBackgroundDataSource

・非同期でデータを持ってくるクラス。
・TasksDataSourceインターフェースをimplementsしている。
・(ネットワークからデータを持ってくる処理を書くと処理が煩雑になるので、別スレッド立ててデータ埋め込んでいるだけ)

TasksDataSource

・データ取得の処理関係をまとめたインターフェース。

Task

・取得した値を管理するデータクラス。

〇コールバック部分抜粋

(注)理解しやすいために上のクラス一覧とは逆順に記載

データ取得の関数とコールバックとして呼ばれる関数を定義

TasksDataSource
public interface TasksDataSource {

    // コールバックとして呼ばれる関数
    interface LoadTasksCallback {
        void onTasksLoaded(List<Task> tasks);
        void onDataNotAvailable();
    }

    // データ取得関数(別に書かなくてよいがまとめて書いておく)
    void getTasks(@NonNull LoadTasksCallback callback);
}

データ取得関数getTasksを実装
取得後はコールバック関数を呼んであげる

TasksBackgroundDataSource
public 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のインスタンスを生成して呼び出してほしい関数を実装して渡す。

TasksRepository
        mTasksBackgroundSource.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が呼ばれていくという仕組みでした。
取得先も増えてくるとわけわからなくなってくるので、やっぱりライブラリを使いましょうということですね。
とりあえず基本をまとめてみました。

〇参考

android/architecture-samples

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] コピーできないテキストをコピーする

Androidで選択できない(textIsSelectable=false)テキストをコピーする方法。
スクショだと取り回しづらいし長文だと地獄なので、見つけた方法をメモしておく。

結論から言えば、uiautomator dumpコマンドで表示しているテキストの全文を取得できる。
たとえ選択できる場合でも、PCでゴニョゴニョしたい場合は、「選択」→「共有」より便利。
確認はAndroid10で実施した。

Screenshot_1571657754.png

この状態で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と表示との関係がよくわからない場合はこれらを使えばいい。
TextViewmTextを選択してCtrl+Cすればコピー可能。

  • Layout Inspector

Layout Inspectorのスクリーンショット

  • uiautomatorviewer

uiautomatorviewerのスクリーンショット

参考

Test UI for multiple apps - Android Developer
Layout Inspector - Android Developer
TextView#textIsSelectable - Android Developer
UiAutomatorについて - Qiita
寿限無 - Wiipedia

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacターミナルからAPKファイルをAndroidの実機端末にインストール

前提条件

Android Studioが入っていること

手順

  • bash_profileにSDKのパスを通す
  • インストールを行う。

bash_profileにSDKのパスを通す

ターミナルを開き以下のコマンドを入力します。

vim ~/.bash_profile

先ほど確認したAndroid SDKのパスを記入します

export PATH=$PATH:/Users/ユーザ名/Library/Android/sdk/platform-tools

Android Studioを使用しているので同環境の方はSDKのパスのユーザ名のみの変更で問題ないかと思います。
※SDKのパスはAndroid Studioで「File」>「Other Settings」>「Default Project Structure…」ここに記載されています。
alt

bash_profileを再度読み込む

方法は二つあります。
1.ターミナルを再起動

2.コマンドで改めて読み込む

source ~/.bash_profile

adbコマンドを利用することができます。

インストール

adb install xxx.apk

xxx.apkはapkファイルまでのパスを入れてください。
Successが表示されたらインストール完了です。
Android Studioからrunするのとは違って自動ではアプリは開かれないです。

補足

  • adbとは『Android Debug Bridge』の略でAndroidのデバッグをサポートするツールです。 adbはAndroid SDKに含まれていますので、開発マシンにパスを通せば使えるようになります。
  • androidとUSB接続時にデバックモードないと反映されません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

https://medium.com/flutter-community/building-flutter-apps-with-wordpress-backend-part-1-e56414a4a79b


Ritesh Sharmaが、WordpressをFlutterアプリのバックエンドとして使用する方法を説明します。

Gitpod + Flutter =モバイルアプリ開発者向けの外出先での生産性

https://medium.com/@jacksonz666/gitpod-flutter-productivity-on-the-go-for-mobile-app-developers-cc2495049d52


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用の非常にシンプルな(まだ強力な)暗黙的なアニメーションライブラリ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を読み込ませてアプリを起動

https___qiita-image-store.s3.amazonaws.com_0_6817_8ca40d0d-b427-6ae4-778d-f1e4cbdca5dd.jpeg

JSON Representationの部分に上記で作成したJSONを設定してStart Session をすることで
端末からpullしたapkを端末に送り込み、インスペクタが起動します。

Appium-inspector.png

上記のインスペクタを使えばxpathが取得できるので、あとはそれをつかってseleniumを書くようにスクリプトを書いたり、レコーディングの機能を使って端末の操作をコード化することができます。

コードは自分はWebdriver.ioで記述しますが、Ruby,Java,PythonなどでもOKです。
ここはSeleniumの話になってしまうので割愛します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

https://segmentfault.com/a/1190000015345637

https://github.com/esauvisky/PGo-CalcaBotaBotaCalca

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の縛りを突破したいですが・・・。

アプリとしては、まだまだ不完全ですが、今後、色々と実験がてら拡張していこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.gradle
dependencies {
    implementation 'jp.wasabeef:recyclerview-animators:3.0.0'
}

を追加します。

2. 使用したいアニメーションをRecyclerViewにセット

build.gradleに追加が終わったら、RecyclerViewのitemAnimatorに動作させたいアニメーションを記述します。

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    recyclerView.itemAnimator = SlideInLeftAnimator() // 左からスライドAnimationが実行される
}

3. RecyclerViewにitem追加と削除の処理を書く

最後に、RecyclerViewにitemを追加と削除するときの処理を記述します。
今回はGroupieを使用しているため、以下のように書きました。

MainActivity.kt
    private 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)
        }
    }

このように書くことでアニメーションが動作します!

スライドイン スライドアウト
SlideInLeftAnimator.gif SlideOutLeftAnimator.gif

他にもフェードイン・アウトアニメーションやフリップアニメーション、
また動作方向やアニメーション変化を変更することができるみたいです。

まとめ

アニメーションをつけることは本来難しいのですが、とても簡単に実装できるため、
これから使っていこうと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む