快速反覆開發 Android 應用程式
本頁說明 bazel mobile-install
如何大幅加快 Android 的疊代開發速度。並說明這種做法的優點,以及分開建構和安裝步驟的缺點。
摘要
如要快速安裝 Android 應用程式的小幅變更,請按照下列步驟操作:
- 找出要安裝應用程式的
android_binary
規則。 - 將裝置連上
adb
。 - 執行
bazel mobile-install :your_target
。 應用程式啟動速度會比平常慢一點。 - 編輯程式碼或 Android 資源。
- 執行
bazel mobile-install :your_target
。 - 享受快速且極簡的漸進式安裝體驗!
以下是 Bazel 的一些實用指令列選項:
--adb
會告知 Bazel 要使用哪個 adb 二進位檔--adb_arg
可用於在adb
的指令列中新增額外引數。 這項功能的一項實用應用是,如果工作站連線多部裝置,您可以選取要安裝的裝置:bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>
如有疑問,請參閱範例、透過 Google 討論群組與我們聯絡,或在 GitHub 回報問題。
簡介
開發人員工具鍊最重要的屬性之一就是速度:變更程式碼後,如果能在一秒內看到程式碼執行,與必須等待數分鐘 (有時甚至數小時) 才能獲得回饋,瞭解變更是否符合預期,兩者之間有天壤之別。
遺憾的是,傳統的 Android 工具鍊建構 .apk 時,需要許多單一的循序步驟,而且必須完成所有步驟才能建構 Android 應用程式。在 Google,如果專案較大 (例如 Google 地圖),建構單行變更內容時等待五分鐘並不罕見。
bazel mobile-install
結合了變更修剪、工作分片和 Android 內部巧妙操作,可大幅加快 Android 的疊代開發速度,而且完全不需要變更任何應用程式程式碼。
傳統應用程式安裝問題
建構 Android 應用程式時可能會遇到一些問題,包括:
Dexing。根據預設,Dexer 工具 (過去為
dx
,現在為d8
或r8
) 會在建構中叫用一次,且不知道如何重複使用先前建構中的工作:即使只有一個方法變更,也會再次執行每個方法的 Dex。將資料上傳至裝置。ADB 不會使用 USB 2.0 連線的完整頻寬,因此上傳較大的應用程式可能需要很長時間。即使只有小部分變更 (例如資源或單一方法),系統仍會上傳整個應用程式,因此這可能會成為重大瓶頸。
編譯為原生程式碼。Android L 導入了新的 Android 執行階段 ART,可預先編譯應用程式,而非像 Dalvik 一樣即時編譯。這會大幅加快應用程式速度,但安裝時間會較長。對使用者來說,這項取捨是值得的,因為他們通常只會安裝一次應用程式,但會多次使用。不過,如果應用程式安裝次數很多,且每個版本最多只會執行幾次,開發速度就會變慢。
bazel mobile-install
的做法
bazel mobile-install
進行下列改善:
分片去糖化和 dexing。建構應用程式的 Java 程式碼後,Bazel 會將類別檔案分成大小相近的幾部分,並分別對這些部分叫用
d8
。如果分片自上次建構以來沒有變更,就不會叫用d8
。然後編譯成獨立的 APK 分片。增量檔案傳輸。系統會從主要 .apk 移除 Android 資源、.dex 檔案和原生程式庫,並儲存在獨立的行動裝置安裝目錄中。這樣一來,您就能獨立更新程式碼和 Android 資源,不必重新安裝整個應用程式。因此,傳輸檔案所需的時間較少,且只有變更過的 .dex 檔案會在裝置上重新編譯。
已分片的安裝作業。行動裝置安裝功能會使用 Android Studio 的
apkdeployer
工具,在已連線的裝置上合併分片 APK,提供連貫一致的體驗。
已分片的 Dex 處理
分片 dexing 相當簡單:建構 .jar 檔案後,工具會將這些檔案分成大小約略相等的 .jar 檔案,然後在上次建構後變更的檔案上叫用 d8
。決定要 dex 哪些分片的邏輯並非 Android 專屬,而是使用 Bazel 的一般變更修剪演算法。
分片演算法的第一個版本只是依字母順序排序 .class 檔案,然後將清單分割成大小相等的部分,但這證明並非最佳做法:如果新增或移除類別 (即使是巢狀或匿名類別),該類別之後的所有類別都會依字母順序位移一個位置,導致這些分片再次進行 dexing。因此,我們決定將 Java 套件分片,而非個別類別。當然,如果新增或移除套件,這仍會導致許多分片建立索引,但這比新增或移除單一類別的頻率低得多。
分片數量由指令列設定控制,使用 --define=num_dex_shards=N
旗標。在理想情況下,Bazel 會自動判斷最佳分片數量,但 Bazel 目前必須先瞭解動作集 (例如建構期間要執行的指令),才能執行任何動作,因此無法判斷最佳分片數量,因為 Bazel 不知道應用程式最終會有多少 Java 類別。一般來說,分片越多,建構和安裝速度就越快,但應用程式啟動速度會變慢,因為動態連結器必須執行更多工作。通常介於 10 到 50 個分片之間。
增量部署
現在,增量 APK 分片轉移和安裝作業是由「行動安裝方法」一節所述的 apkdeployer
公用程式處理。舊版 (原生) 行動應用程式安裝廣告需要手動追蹤首次安裝次數,並在後續安裝時選擇性套用 --incremental
標記,但 rules_android
最新版本已大幅簡化。無論應用程式安裝或重新安裝幾次,都可以使用相同的行動裝置安裝呼叫。
從高層次來看,apkdeployer
工具是各種 adb
子指令的包裝函式。主要進入點邏輯位於 com.android.tools.deployer.Deployer
類別中,其他公用程式類別則位於同一套件中。Deployer
類別會接收 APK 分割路徑清單和含有安裝資訊的 Protobuf,並運用 Android 應用程式套件的部署功能建立安裝工作階段,逐步部署應用程式分割。如需實作詳情,請參閱 ApkPreInstaller
和 ApkInstaller
類別。
結果
成效
一般來說,bazel mobile-install
可將小型變更後建構及安裝大型應用程式的速度提升 4 到 10 倍。
以下是幾項 Google 產品的計算結果:
當然,這取決於變更的性質:變更基礎程式庫後重新編譯需要更多時間。
限制
但這些技巧並非在所有情況下都適用。 以下案例說明無法正常運作的情況:
行動裝置安裝廣告只能透過
rules_android
的 Starlark 規則支援。 詳情請參閱「行動裝置安裝次數簡史」。僅支援執行 ART 的裝置。行動裝置安裝功能會使用 API 和執行階段功能, 這些功能只存在於執行 ART 的裝置,而非 Dalvik。只要是比 Android L (API 21 以上) 更新的 Android 執行階段,都應該相容。
Bazel 本身必須使用工具 Java 執行階段和語言版本 17 以上執行。
如果 Bazel 版本低於 8.4.0,必須為 mobile-install 指定一些額外標記。請參閱 Bazel Android 教學課程。這些標記會告知 Bazel Starlark 行動安裝層面所在位置,以及支援哪些規則。
行動應用程式安裝廣告簡史
舊版 Bazel 原生內建熱門語言和生態系統 (例如 C++、Java 和 Android) 的建構和測試規則。因此這些規則稱為「原生」規則。Bazel 8 (於 2024 年發布) 移除了對這些規則的支援,因為許多規則已遷移至 Starlark 語言。詳情請參閱「Bazel 8.0 LTS 網誌文章」。
舊版原生 Android 規則也支援舊版原生行動應用程式安裝功能。這就是現在所謂的「行動裝置應用程式安裝廣告第 1 版」或「原生行動裝置應用程式安裝廣告」。這項功能已在 Bazel 8 中刪除,內建的 Android 規則也一併刪除。
現在,所有行動裝置安裝功能,以及所有 Android 建構和測試規則,都已在 Starlark 中實作,並位於 rules_android
GitHub 存放區。最新版本稱為「行動裝置應用程式安裝廣告 v3」或「MIv3」。
命名注意事項:Google 內部曾提供「mobile-install v2」,但從未對外發布,只有 v3 繼續用於 Google 內部和 OSS rules_android 部署。