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 アプリのビルドには、次のような問題があります。
Dex 変換。デフォルトでは、Dexer ツール(以前は
dx、現在はd8またはr8)はビルドで 1 回だけ呼び出され、以前のビルドの作業を再利用する方法がわかりません。1 つのメソッドだけが変更された場合でも、すべてのメソッドを再度 dex します。デバイスへのデータのアップロード。adb は USB 2.0 接続の帯域幅をすべて使用しないため、サイズの大きいアプリのアップロードには時間がかかることがあります。変更された部分がリソースや単一のメソッドなど、ごく一部であっても、アプリ全体がアップロードされるため、大きなボトルネックになる可能性があります。
ネイティブ コードへのコンパイル。Android L では、新しい Android ランタイムである ART が導入されました。ART は、Dalvik のようにジャストインタイムでコンパイルするのではなく、事前コンパイルを行います。これにより、アプリの速度は大幅に向上しますが、インストール時間が長くなります。ユーザーにとっては、アプリをインストールするのは通常 1 回で、使用するのは何度もであるため、これは妥当なトレードオフです。しかし、アプリが何度もインストールされ、各バージョンが数回しか実行されないような場合は、開発が遅くなります。
bazel mobile-install のアプローチ
bazel mobile-install では、次の点が改善されています。
シャーディングされた脱糖と dex 化。アプリの 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 を実行しているデバイスのみがサポートされます。モバイル インストールでは、Dalvik ではなく 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 のみが引き続き使用されています。