bazel モバイル インストール

Android の迅速な反復開発

このページでは、bazel mobile-install を使用して Android の反復開発 を大幅に高速化する方法について説明します。このアプローチのメリットと、 ビルドとインストールの手順を別々に行うことのデメリットについて説明します。

概要

Android アプリに小さな変更をすばやくインストールするには、次の操作を行います。

  1. インストールするアプリの android_binary ルールを見つけます。
  2. デバイスを adb に接続します。
  3. bazel mobile-install :your_target を実行します。アプリの起動が通常より少し 遅くなります。
  4. コードまたは Android リソースを編集します。
  5. bazel mobile-install :your_target を実行します。
  6. 高速で最小限の増分インストールをお楽しみください。

Bazel のコマンドライン オプションで役立つものをいくつか紹介します。

  • --adb は、使用する adb バイナリを Bazel に伝えます。
  • --adb_arg を使用すると、adb のコマンドラインに追加の引数を追加できます。 このオプションの便利な用途の一つに、ワークステーションに複数のデバイスが接続されている場合に、インストール先のデバイスを選択することがあります。 to if you have multiple devices connected to your workstation: bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>

不明な点がある場合は、 を参照するか、 Google グループにお問い合わせいただくか、 GitHub の問題を報告してください。

はじめに

デベロッパーのツールチェーンの最も重要な属性の一つに速度があります。コードを変更して 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 では、次の点が改善されています。

  • シャーディングされた desugaring と dex 変換。アプリの Java コードをビルドした後、Bazel はクラスファイルをほぼ同じサイズのパーツに分割し、それらに対して個別に d8 を呼び出します。d8 は、前回のビルドから変更されていないシャードに対して呼び出されません。これらのシャードは、個別のシャーディングされた APK にコンパイルされます。

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

  • シャーディングされたインストール。mobile-install は、Android Studio の apkdeployer ツールを使用して、接続されたデバイス上のシャーディングされた APK を結合し、まとまりのある エクスペリエンスを提供します。

シャーディングされた dex 変換

シャーディングされた dex 変換は比較的簡単です。.jar ファイルがビルドされると、a ツール によってほぼ同じサイズの個別の .jar ファイルに分割され、前回のビルドから変更されたファイルに対して d8が呼び出されます。dex 変換するシャードを決定するロジックは Android に固有のものではなく、Bazel の一般的な変更プルーニング アルゴリズムを使用します。

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

シャードの数は、 --define=num_dex_shards=N フラグを使用して、コマンドライン構成で制御します。理想的には、Bazel が 最適なシャード数を自動的に判断しますが、Bazel は現在、アクション(ビルド中に実行されるコマンドなど)のセットを把握してから 実行する必要があるため、アプリに最終的に含まれる Java クラスの数がわからないため、最適なシャード数を判断できません。 一般的に、シャードが多いほど、ビルドと インストールの速度は速くなりますが、動的 リンカーの処理が増えるため、アプリの起動が遅くなります。通常、最適なシャード数は 10 ~ 50 個です。

増分デプロイ

増分 APK シャードの転送とインストールは、 apkdeployer ユーティリティによって、「mobile-install のアプローチ」で説明されているように処理されるようになりました。 以前の(ネイティブ)バージョンの mobile-install では、初回インストールを手動で追跡し、以降のインストールで --incremental フラグを選択的に適用する必要がありましたが、rules_android の最新バージョンでは大幅に簡素化されています。アプリがインストール または再インストールされた回数に関係なく、同じ mobile-install 呼び出しを使用できます。

大まかに言うと、apkdeployer ツールはさまざまな adb サブコマンドのラッパーです。メインのエントリ ポイント ロジックは com.android.tools.deployer.Deployer クラスにあり、他のユーティリティ クラスは同じパッケージに配置されています。 Deployer クラスは、分割された APK のパスのリストやインストールに関する情報を含む protobuf などを取り込み、 Android App Bundle のデプロイ機能を利用してインストール セッションを作成し、アプリの分割を増分的にデプロイします。 実装の詳細については、ApkPreInstaller クラスと ApkInstaller クラスをご覧ください。

結果

パフォーマンス

一般的に、bazel mobile-install を使用すると、小さな変更後の大規模なアプリのビルド とインストールが 4 ~ 10 倍高速化されます。

次の数値は、いくつかの Google プロダクトで計算されたものです。

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

制限事項

スタブ アプリケーションのトリックは、すべてのケースで機能するわけではありません。 次のようなケースでは、期待どおりに動作しません。

  • mobile-install は、rules_android の Starlark ルールでのみサポートされています。 詳細については、"mobile-install の簡単な歴史"を ご覧ください。

  • ART を実行しているデバイスのみがサポートされています。mobile-install は、Dalvik ではなく ART を実行しているデバイスにのみ存在する API とランタイム機能 を使用します。Android L(API 21 以降)より新しい Android ランタイムは互換性があるはずです。

  • Bazel 自体は、ツール Java ランタイム 17 以上の言語バージョン で実行する必要があります。

  • 8.4.0 より前の Bazel バージョンでは、 mobile-install 用に追加のフラグを指定する必要があります。Bazel Android チュートリアルをご覧ください。これらの フラグは、Starlark mobile-install アスペクトの場所と、サポートされている ルールを Bazel に通知します。

mobile-install の簡単な歴史

以前の Bazel バージョンには、C++、Java、Android などの 一般的な言語とエコシステム用の組み込みのビルドルールとテストルールがネイティブで含まれていました。そのため、これらのルール はネイティブルールと呼ばれていました。Bazel 8(2024 年リリース)では、これらのルールの多くが Starlark 言語に移行されたため、これらのルールのサポートが削除されました。詳細については、"Bazel 8.0 LTS ブログ投稿" をご覧ください。

従来のネイティブ Android ルールでは、mobile-install 機能の従来の ネイティブ バージョンもサポートされていました。 これは現在、「mobile-install v1」または 「ネイティブ mobile-install」と呼ばれています。この機能は、組み込みの Android ルールとともに Bazel 8 で削除されました。

現在、すべての mobile-install 機能とすべての Android ビルドルールとテスト ルールは Starlark で実装され、rules_android GitHub リポジトリに存在します。最新バージョンは「mobile-install v3」または「MIv3」と呼ばれています。

命名に関する注意: Google 社内でのみ利用可能な「mobile-install v2」がかつて存在しましたが、外部に公開されることはありませんでした。Google 社内と OSS の rules_android デプロイの両方で v3 のみが引き続き使用されています。