Android 向けの高速な反復開発
このページでは、bazel mobile-install
によって Android の反復開発が大幅に高速化される仕組みについて説明します。このアプローチのメリットと、ビルドとインストールを別々のステップで行うことのデメリットについて説明します。
概要
Android アプリに小さな変更をすばやくインストールするには、次の操作を行います。
- インストールするアプリの
android_binary
ルールを見つけます。 - デバイスを
adb
に接続します。 bazel mobile-install :your_target
を実行します。アプリの起動が通常より少し遅くなります。- コードまたは Android リソースを編集します。
bazel mobile-install :your_target
を実行します。- 高速で最小限の増分インストールをお楽しみください。
Bazel で使用できるコマンドライン オプションのうち、特に役立つものを次に示します。
--adb
は、使用する adb バイナリを Bazel に伝えます。--adb_arg
を使用して、adb
のコマンドラインに追加の引数を追加できます。この機能の便利な用途の 1 つは、ワークステーションに複数のデバイスが接続されている場合に、インストール先のデバイスを選択することです。bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>
不明な点がある場合は、例を参照するか、Google グループで問い合わせるか、GitHub で問題を報告してください。
はじめに
デベロッパーのツールチェーンの最も重要な属性の 1 つは速度です。コードを変更してから 1 秒以内に実行結果を確認できるのと、変更が期待どおりに機能するかどうかに関するフィードバックを得るまでに数分、場合によっては数時間も待たなければならないのとでは、大きな違いがあります。
残念ながら、.apk をビルドするための従来の Android ツールチェーンには、多くのモノリシックなシーケンシャル ステップが含まれており、Android アプリをビルドするには、これらのステップをすべて実行する必要があります。Google では、Google マップなどの大規模なプロジェクトで、1 行の変更をビルドするのに 5 分かかることは珍しくありませんでした。
bazel mobile-install
は、変更のプルーニング、作業のシャーディング、Android 内部の巧妙な操作を組み合わせることで、アプリのコードを変更することなく、Android の反復開発を大幅に高速化します。
従来のアプリのインストールに関する問題
Android アプリのビルドには、次のような問題があります。
Dexing。デフォルトでは、Dexer ツール(以前は
dx
、現在はd8
またはr8
)はビルドで 1 回だけ呼び出され、以前のビルドの作業を再利用する方法がわかりません。1 つのメソッドだけが変更された場合でも、すべてのメソッドを再度 dex します。デバイスへのデータのアップロード。adb は USB 2.0 接続の帯域幅をすべて使用しないため、サイズの大きいアプリのアップロードには時間がかかることがあります。リソースや単一のメソッドなど、変更された部分がごく一部であっても、アプリ全体がアップロードされるため、これが大きなボトルネックになる可能性があります。
ネイティブ コードへのコンパイル。Android L では、新しい Android ランタイムである ART が導入されました。ART は、Dalvik のようにジャストインタイムでコンパイルするのではなく、事前コンパイルを行います。これにより、アプリの速度は大幅に向上しますが、インストール時間が長くなります。ユーザーにとっては、アプリをインストールするのは通常 1 回で、何度も使用するため、このトレードオフは妥当です。しかし、アプリが何度もインストールされ、各バージョンが数回しか実行されないような場合は、開発が遅くなります。
bazel mobile-install
のアプローチ
bazel mobile-install
では、次の点が改善されています。
シャーディングされた脱糖と dexing。アプリの Java コードをビルドした後、Bazel はクラスファイルをほぼ同じサイズのパーツに分割し、それらに対して
d8
を個別に呼び出します。前回のビルド以降変更されていないシャードでは、d8
は呼び出されません。これらのシャードは、個別のシャード化された APK にコンパイルされます。増分ファイル転送。Android リソース、.dex ファイル、ネイティブ ライブラリはメインの .apk から削除され、別の mobile-install ディレクトリに保存されます。これにより、アプリ全体を再インストールすることなく、コードと Android リソースを個別に更新できます。そのため、ファイルの転送にかかる時間が短縮され、変更された .dex ファイルのみがデバイス上で再コンパイルされます。
シャーディングされたインストール。モバイル インストールでは、Android Studio の
apkdeployer
ツールを使用して、接続済みデバイス上の分割された APK を結合し、一貫性のあるエクスペリエンスを提供します。
シャーディングされた Dex 変換
シャード化された dexing は比較的簡単です。.jar ファイルがビルドされると、ツールがそれらをほぼ同じサイズの個別の .jar ファイルにシャード化し、前回のビルド以降に変更されたものに対して d8
を呼び出します。どのシャードを dex するかを決定するロジックは Android 固有のものではなく、Bazel の一般的な変更プルーニング アルゴリズムを使用するだけです。
最初のバージョンのシャーディング アルゴリズムでは、.class ファイルをアルファベット順に並べ替え、リストを同じサイズのパーツに分割していましたが、これは最適ではありませんでした。クラスが追加または削除されると(ネストされたクラスや匿名クラスでも)、そのクラスの後に続くすべてのクラスが 1 つずつシフトし、その結果、これらのシャードが再度 dex 化されることになります。そのため、個々のクラスではなく Java パッケージをシャーディングすることにしました。もちろん、新しいパッケージが追加または削除された場合は、多くのシャードのインデックス登録が行われますが、単一のクラスの追加または削除よりも頻度ははるかに少なくなります。
シャードの数は、--define=num_dex_shards=N
フラグを使用してコマンドライン構成で制御されます。理想的には、Bazel が最適なシャード数を自動的に決定しますが、Bazel は現在、アクションのセット(ビルド中に実行されるコマンドなど)を認識してからアクションを実行する必要があるため、アプリに最終的に含まれる Java クラスの数がわからないと、最適なシャード数を決定できません。一般的に、シャード数が多いほど、ビルドとインストールは高速になりますが、動的リンカーの作業が増えるため、アプリの起動は遅くなります。通常、最適なシャード数は 10 ~ 50 個です。
増分デプロイ
APK のインクリメンタル シャードの転送とインストールは、「モバイル インストールの方法」で説明されている apkdeployer
ユーティリティによって処理されるようになりました。以前の(ネイティブ)バージョンのモバイル インストールでは、初回インストールを手動でトラッキングし、後続のインストールで --incremental
フラグを選択的に適用する必要がありましたが、rules_android
の最新バージョンでは大幅に簡素化されています。アプリのインストールまたは再インストールの回数に関係なく、同じモバイル インストール呼び出しを使用できます。
大まかに言うと、apkdeployer
ツールはさまざまな adb
サブコマンドのラッパーです。メインのエントリ ポイントのロジックは com.android.tools.deployer.Deployer
クラスにあり、他のユーティリティ クラスは同じパッケージに配置されています。Deployer
クラスは、分割 APK のパスのリストやインストールに関する情報を含む protobuf などを取り込み、Android App Bundle のデプロイ機能を利用して、インストール セッションを作成し、アプリの分割を段階的にデプロイします。実装の詳細については、ApkPreInstaller
クラスと ApkInstaller
クラスをご覧ください。
結果
パフォーマンス
一般的に、bazel mobile-install
を使用すると、小さな変更後の大きなアプリのビルドとインストールが 4 ~ 10 倍速くなります。
以下の数値は、いくつかの Google サービスについて計算されたものです。
もちろん、これは変更の性質によって異なります。ベース ライブラリを変更した後の再コンパイルには時間がかかります。
制限事項
スタブ アプリケーションが実行するトリックは、すべてのケースで有効というわけではありません。次のケースでは、想定どおりに動作しません。
モバイル インストールは
rules_android
の Starlark ルールでのみサポートされます。詳しくは、「モバイル インストールの簡単な歴史」をご覧ください。ART を実行しているデバイスのみがサポートされます。モバイル インストールでは、ART を実行するデバイスにのみ存在する API とランタイム機能を使用します。Android L(API 21 以降)より新しい Android ランタイムであれば、互換性があるはずです。
Bazel 自体は、ツール Java ランタイムと 17 以降の言語バージョンで実行する必要があります。
8.4.0 より前の Bazel バージョンでは、モバイル インストール用にいくつかの追加フラグを指定する必要があります。Bazel Android チュートリアルをご覧ください。これらのフラグは、Starlark モバイル インストール アスペクトの場所とサポートされているルールを Bazel に通知します。
モバイルアプリ インストール広告の簡単な歴史
以前の Bazel バージョンには、C++、Java、Android などの一般的な言語やエコシステム用のビルドとテストのルールがネイティブに含まれていました。そのため、これらのルールはネイティブ ルールと呼ばれていました。Bazel 8(2024 年にリリース)では、これらのルールの多くが Starlark 言語に移行されたため、これらのルールのサポートが削除されました。詳しくは、「Bazel 8.0 LTS ブログ投稿」をご覧ください。
以前のネイティブ Android ルールでは、以前のネイティブ バージョンのモバイル インストール機能もサポートされていました。これは現在、「モバイル インストール v1」または「ネイティブ モバイル インストール」と呼ばれています。この機能は、Bazel 8 で組み込みの Android ルールとともに削除されました。
現在、モバイル インストール機能と Android のビルドおよびテストルールはすべて Starlark で実装され、rules_android
GitHub リポジトリに存在します。最新バージョンは「モバイル インストール v3」または「MIv3」と呼ばれます。
命名に関する注記: Google の内部でのみ「mobile-install v2」が利用可能だった時期がありましたが、これは外部に公開されることはなく、Google 内部と OSS の rules_android デプロイの両方で v3 のみが引き続き使用されています。