bazel モバイル インストール

問題を報告する ソースを表示

Android 向けの迅速な反復型開発

このページでは、bazel mobile-install を使用して Android の反復型開発を高速化する方法について説明します。このアプローチの利点と、従来のアプリ インストール方法の課題について説明します。

まとめ

Android アプリに小さな変更をすばやくインストールする手順は次のとおりです。

  1. インストールするアプリの android_binary ルールを見つけます。
  2. proguard_specs 属性を削除して、Proguard を無効にします。
  3. multidex 属性を native に設定します。
  4. dex_shards 属性を 10 に設定します。
  5. Dalvik ではなく ART を実行するデバイスを USB 経由で接続し、USB デバッグを有効にします。
  6. bazel mobile-install :your_target を実行します。アプリの起動が通常より少し遅くなります。
  7. コードまたは Android リソースを編集します。
  8. bazel mobile-install --incremental :your_target を実行します。
  9. 待ち時間が少なくて済みます。

Bazel に関する便利なコマンドライン オプションを以下に示します。

  • --adb は、使用する adb バイナリを Bazel に指示します
  • --adb_arg を使用すると、adb のコマンドラインに引数を追加できます。複数のデバイスをワークステーションに接続している場合に、インストールするデバイスを選択すると便利です。bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app が自動的にアプリを起動します

判断がつかない場合は、をご覧になるか、お問い合わせください。

はじめに

デベロッパーのツールチェーンの最も重要な属性の 1 つは速度です。コードを変更して 1 秒以内に実行することには違いがあり、変更によって期待どおりの動作が得られるかどうかについてフィードバックを得るまでに数分かかる場合があります。

残念ながら、.apk をビルドするための従来の Android ツールチェーンは、Android アプリをビルドするために多くのモノリシックな連続ステップを必要とします。Google マップでは、Google マップのような大規模なプロジェクトで、1 行の変更を 5 分間待機することは珍しいことではありません。

bazel mobile-install では、アプリのコードを変更することなく、Android 内部の変更のプルーニング、作業のシャーディング、巧妙な操作を組み合わせることで、Android の反復型開発を大幅に高速化できます。

従来のアプリのインストールに関する問題

Android アプリのビルドには、次のような問題があります。

  • デックス。デフォルトでは、「dx」はビルド内で正確に 1 回だけ呼び出されます。以前のビルドの作業を再利用する方法は認識されておらず、1 つのメソッドだけが変更された場合でも、すべてのメソッドが再度 dex されます。

  • デバイスにデータをアップロードする。adb は USB 2.0 接続の全帯域幅を使用しないため、大きなアプリのアップロードには長い時間がかかります。リソースや単一のメソッドなど、小さな部分のみが変更された場合でも、アプリ全体がアップロードされるため、大きなボトルネックになる可能性があります。

  • ネイティブ コードにコンパイルします。Android L では、新しい Android ランタイムである ART が導入されました。これは、Dalvik のようにアプリをジャスト イン コンパイルするのではなく、事前にコンパイルするものです。これにより、インストール時間が長くなり、アプリの動作速度が大幅に向上します。通常はアプリを 1 回インストールして何度も使用することになるため、これはユーザーにとってトレードオフになりますが、その結果、アプリが何度もインストールされ、各バージョンの実行回数は多くありません。

bazel mobile-install のアプローチ

bazel mobile-install は以下の点を改善しています。

  • シャーディングされた dex 変換。アプリの Java コードをビルドすると、Bazel はクラスファイルをほぼ同じサイズにシャーディングし、それぞれで dx を呼び出します。前回のビルドから変更されていないシャードでは、dx が呼び出されません。

  • 増分ファイル転送。Android リソース、.dex ファイル、ネイティブ ライブラリはメインの .apk から削除され、別のモバイル インストール ディレクトリに保存されます。これにより、アプリ全体を再インストールすることなく、コードと Android リソースを個別に更新できます。したがって、ファイルの転送にかかる時間が短くなり、変更された .dex ファイルのみがデバイス上で再コンパイルされます。

  • .apk の外部からアプリの一部を読み込むデバイス上のスタブ インストール ディレクトリから Android リソース、Java コード、ネイティブ コードを読み込む小さなスタブ アプリが .apk に入力され、実際のアプリに制御が転送されます。以下で説明するいくつかの特殊なケースを除き、アプリはすべて透過的です。

シャーディングされたデックス

シャーディングされた dex はかなり簡単です。.jar ファイルがビルドされると、ほぼ同じサイズの別の .jar ファイルにシャーディングされ、前回のビルド以降に変更されたファイルでは dx が呼び出されます。dex するシャードを判断するロジックは、Android に固有のものではなく、Bazel の一般的な変更プルーニング アルゴリズムを使用するだけです。

シャーディング アルゴリズムの最初のバージョンは、.class ファイルをアルファベット順で単純に並べ、次にリストを同じサイズの部分に切り取りましたが、これは最適でないことが判明しました。クラスを追加または削除すると(ネストされたクラスや匿名のクラスでも)、アルファベット順に 1 つ増え、これらのシャードが再度 dex されます。そこで、個々のクラスではなく Java パッケージをシャーディングすることを決定しました。もちろん、これにより新しいパッケージが追加または削除されても、多くのシャードが dex 変換されますが、単一のクラスを追加または削除するよりも、はるかに頻度が低くなります。

シャードの数は、BUILD ファイルによって制御されます(android_binary.dex_shards 属性を使用)。Bazel は理想的なシャードの数を自動的に検出しますが、Bazel は現在、一連のアクション(ビルド時に実行されるコマンドなど)を実行する前にそのシャードを認識する必要があります。したがって、シャードの最適な数を判別することはできません。最終的には、シャードが多ければ多いほど、試行が高速になるほど、試行時間が高速になります。スイート スポットは通常 10 ~ 50 個のシャードです。

増分ファイル転送

アプリをビルドしたら、次のステップでは、できればできるだけ労力をかけずにアプリをインストールします。インストールは次のステップで構成されます。

  1. .apk のインストール(通常は adb install を使用)
  2. .dex ファイル、Android リソース、ネイティブ ライブラリを mobile-install ディレクトリにアップロードする

最初のステップではインクリメンタリティはそれほど大きくありません。アプリがインストールされるかどうかは関係ありません。Bazel は現在、必要に応じて --incremental コマンドライン オプションを使用してこの手順を行うかどうかを示す必要があります。

2 番目のステップでは、ビルドのアプリのファイルをデバイス上のマニフェスト ファイルと比較し、デバイス上のアプリファイルとそのチェックサムをリストします。新しいファイルはデバイスにアップロードされ、変更されたファイルは更新され、削除されたファイルはデバイスから削除されます。マニフェストが存在しない場合は、すべてのファイルをアップロードする必要があると見なされます。

デバイス上のファイルを変更することで増分インストール アルゴリズムを欺くことはできますが、マニフェストでそのチェックサムを変更することはできません。これは、デバイス上のファイルのチェックサムを計算することで保護できましたが、インストール時間の増加には値しないと見なされました。

スタブ アプリケーション

スタブアプリは、デバイス上の mobile-install ディレクトリから dex、ネイティブ コード、Android リソースを読み込むマジックです。

実際の読み込みは BaseDexClassLoader をサブクラス化することで実装され、合理的に文書化されている手法を使用します。これは、アプリのクラスが読み込まれる前に行われるため、apk 内のすべてのアプリクラスをデバイス上の mobile-install ディレクトリに配置して、adb install なしで更新できるようにしています。

これは、アプリのクラスが読み込まれる前に行う必要があるため、アプリケーション クラスを .apk に含める必要はありません。それらのクラスを変更するには、完全に再インストールする必要があります。

これを行うには、AndroidManifest.xml で指定された Application クラスをスタブ アプリケーションに置き換えます。これによってアプリの起動時に制御が受けられ、Android フレームワークの内部で Java のリフレクションを使用して、最も早い段階(コンストラクタ)に合わせてクラスローダーとリソース マネージャーが適切に調整されます。

スタブアプリは、モバイル インストールによってインストールされたネイティブ ライブラリを別の場所にコピーします。動的リンカーはファイルに X ビットを設定する必要があるため、これが必要となります。これは、非 root の adb がアクセスできる場所には設定できないためです。

これらすべてが完了すると、スタブアプリは実際の Application クラスをインスタンス化し、スタブアプリはそれ自体へのすべての参照を Android フレームワーク内の実際のアプリに変更します。

結果

パフォーマンス

一般的に、小さな変更で大規模なアプリの構築とインストールは、4 ~ 10 倍高速になります。bazel mobile-install

以下の数値は、いくつかの Google プロダクトで計算されました。

もちろんこれは、変更の性質によって異なります。ベース ライブラリの変更後に再コンパイルするには時間がかかります。

制限事項

スタブ アプリが行うトリックは、常に機能するとは限りません。想定どおりに動作しない場合を以下に示します。

  • ContextContentProvider#onCreate()Application クラスにキャストされたとき。このメソッドは、アプリの起動時に Application クラスのインスタンスを置き換える前に呼び出されます。したがって、ContentProvider は引き続き実際のスタブではなくスタブ アプリケーションを参照します。おそらく、これは Context をダウンキャストする想定ではないため、これはバグではありませんが、Google の一部のアプリではこの現象が発生するようです。

  • bazel mobile-install によってインストールされたリソースは、アプリ内からのみ使用できます。PackageManager#getApplicationResources() を介して他のアプリがリソースにアクセスする場合、これらのリソースは最後の非増分インストールから取得されます。

  • ART を実行していないデバイス。スタブ アプリケーションは Froyo 以降で正常に動作しますが、特定のケース(Java アノテーションが特定の方法)で使用された場合など、特定の状況でコードが配布されると、アプリが正しくないと判断されるバグが Dalvik に存在します。アプリがこれらのバグを修正していない限り、Dalvik にも対応する必要があります(ただし、古い Android バージョンのサポートは Google の焦点ではありません)。