bazel 行動安裝

回報問題 查看來源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

快速反覆開發 Android 應用程式

本頁說明 bazel mobile-install 如何大幅加快 Android 的疊代開發速度。並說明相較於傳統應用程式安裝方法,這種做法的優點和挑戰。

摘要

如要快速安裝 Android 應用程式的小幅變更,請按照下列步驟操作:

  1. 找出要安裝應用程式的 android_binary 規則。
  2. 移除 proguard_specs 屬性,即可停用 Proguard。
  3. multidex 屬性設為 native
  4. dex_shards 屬性設為 10
  5. 透過 USB 連接執行 ART (而非 Dalvik) 的裝置,並啟用 USB 偵錯功能。
  6. 執行 bazel mobile-install :your_target。 應用程式啟動速度會比平常慢一點。
  7. 編輯程式碼或 Android 資源。
  8. 執行 bazel mobile-install --incremental :your_target
  9. 享受不必等待太久的便利。

以下是 Bazel 的一些實用指令列選項:

  • --adb 會告知 Bazel 要使用哪個 adb 二進位檔
  • --adb_arg 可用於在 adb 的指令列中新增額外引數。 這項功能的一項實用應用是,如果工作站連線多部裝置,您可以選取要安裝的裝置: bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app 會自動啟動應用程式

如有疑慮,請參閱範例與我們聯絡

簡介

開發人員工具鍊最重要的屬性之一就是速度:變更程式碼後,如果能在一秒內看到程式碼執行,與必須等待數分鐘 (有時甚至數小時) 才能獲得回饋,瞭解變更是否符合預期,兩者之間有天壤之別。

遺憾的是,傳統的 Android 工具鍊建構 .apk 時,需要許多單一的循序步驟,而且必須完成所有步驟才能建構 Android 應用程式。在 Google,如果專案較大 (例如 Google 地圖),建構單行變更內容時等待五分鐘並不罕見。

bazel mobile-install 會結合變更修剪、工作分片和 Android 內部巧妙操作,大幅加快 Android 的疊代開發速度,而且完全不會變更任何應用程式程式碼。

傳統應用程式安裝問題

建構 Android 應用程式時可能會遇到一些問題,包括:

  • Dexing。根據預設,「dx」會在建構作業中準確叫用一次,且不知道如何重複使用先前建構作業的工作:即使只變更一個方法,它也會再次 dex 每個方法。

  • 將資料上傳至裝置。ADB 不會使用 USB 2.0 連線的完整頻寬,因此上傳較大的應用程式可能需要很長時間。即使只有小部分變更 (例如資源或單一方法),系統仍會上傳整個應用程式,因此這可能會成為重大瓶頸。

  • 編譯為原生程式碼。Android L 導入了新的 Android 執行階段 ART,可預先編譯應用程式,而非像 Dalvik 一樣即時編譯。這會大幅加快應用程式速度,但安裝時間會較長。對使用者來說,這是不錯的取捨,因為他們通常只會安裝一次應用程式,但會多次使用。不過,如果應用程式安裝次數很多,且每個版本最多只執行幾次,開發速度就會變慢。

bazel mobile-install 的做法

bazel mobile-install 進行了下列改善:

  • 分割 Dex 處理。建構應用程式的 Java 程式碼後,Bazel 會將類別檔案分成大小相近的部分,並分別對這些部分叫用 dx。如果分片自上次建構後未變更,就不會叫用 dx

  • 增量檔案傳輸。系統會從主要 .apk 移除 Android 資源、.dex 檔案和原生程式庫,並儲存在獨立的行動裝置安裝目錄中。這樣一來,您就能獨立更新程式碼和 Android 資源,不必重新安裝整個應用程式。因此,傳輸檔案所需的時間較短,且只有變更過的 .dex 檔案會在裝置上重新編譯。

  • 從 .apk 外部載入應用程式部分內容。系統會將小型 Stub 應用程式放入 .apk,從裝置上的行動安裝目錄載入 Android 資源、Java 程式碼和原生程式碼,然後將控制權轉移至實際應用程式。除了下文所述的少數特殊情況外,這一切對應用程式來說都是透明的。

已分片的 Dex 處理

分片 dexing 相當簡單:建構 .jar 檔案後,工具會將這些檔案分成大小約略相等的 .jar 檔案,然後在上次建構後變更的檔案上叫用 dx。決定要 dex 哪些分片的邏輯並非 Android 專屬,而是使用 Bazel 的一般變更修剪演算法。

分片演算法的第一個版本只是依字母順序排序 .class 檔案,然後將清單分割成大小相等的部分,但這證明並非最佳做法:如果新增或移除類別 (即使是巢狀或匿名類別),該類別之後的所有類別都會依字母順序位移一個位置,導致這些分片再次進行 dexing。因此,我們決定將 Java 套件分片,而非個別類別。當然,如果新增或移除套件,這仍會導致許多分片建立索引,但這比新增或移除單一類別的頻率低得多。

BUILD 檔案會控管分片數量 (使用 android_binary.dex_shards 屬性)。在理想情況下,Bazel 會自動判斷最佳分片數量,但 Bazel 目前必須先瞭解動作集 (例如在建構期間執行的指令),才能執行任何動作,因此無法判斷最佳分片數量,因為 Bazel 不知道應用程式最終會有多少 Java 類別。一般來說,分片越多,建構和安裝速度就越快,但應用程式啟動速度會變慢,因為動態連結器必須執行更多工作。通常介於 10 到 50 個分片之間。

增量檔案傳輸

建構應用程式後,下一步就是安裝應用程式,最好盡可能減少安裝作業。安裝程序包含下列步驟:

  1. 安裝 .apk (通常使用 adb install)
  2. 將 .dex 檔案、Android 資源和原生程式庫上傳至 mobile-install 目錄

第一個步驟的增量並不多:應用程式不是已安裝,就是未安裝。Bazel 目前會依賴使用者透過 --incremental 指令列選項指出是否應執行這個步驟,因為 Bazel 無法在所有情況下判斷是否需要執行這個步驟。

在第二個步驟中,系統會將建構作業產生的應用程式檔案,與裝置上的資訊清單檔案進行比較,找出裝置上的應用程式檔案及其總和檢查碼。系統會將所有新檔案上傳至裝置、更新所有已變更的檔案,並從裝置中刪除所有已移除的檔案。如果沒有資訊清單,系統會假設所有檔案都需要上傳。

請注意,只要變更裝置上的檔案,但不要變更資訊清單中的總和檢查碼,就有可能騙過遞增安裝演算法。計算裝置上檔案的總和檢查碼,或許可以防範這種情況,但我們認為安裝時間不值得因此增加。

虛設常式應用程式

存根應用程式會從裝置上的 mobile-install 目錄載入 DEX 檔案、原生程式碼和 Android 資源。

實際載入作業是透過子類別化 BaseDexClassLoader 實作,且是說明文件相當詳盡的技術。這會在載入任何應用程式類別之前發生,因此 APK 中的任何應用程式類別都可以放在裝置上的 mobile-install 目錄中,以便在不 adb install 的情況下更新。

這項作業必須在載入任何應用程式類別之前完成,這樣 .apk 中就不需要任何應用程式類別,也就是說,如果變更這些類別,就必須重新安裝整個應用程式。

方法是將 AndroidManifest.xmlAndroidManifest.xml 指定的 Application 類別,替換為虛設常式應用程式。應用程式啟動時,這個類別會接管控制權,並在最早的時間點 (建構函式) 使用 Java 反射,適當調整類別載入器和資源管理工具,以處理 Android 架構的內部作業。

此外,虛設常式應用程式還會將行動裝置安裝服務安裝的原生程式庫複製到其他位置。這是必要的,因為動態連結器需要在檔案上設定 X 位元,但非根 adb 可存取的任何位置都無法執行這項操作。

完成上述所有事項後,存根應用程式就會例項化實際的 Application 類別,將所有對自身的參照變更為 Android 架構內的實際應用程式。

結果

成效

一般來說,bazel mobile-install 可將小型變更後建構及安裝大型應用程式的速度提升 4 到 10 倍。

以下是幾項 Google 產品的計算結果:

當然,這取決於變更的性質:變更基礎程式庫後重新編譯需要更多時間。

限制

但這些技巧並非適用於所有情況。 以下案例說明無法正常運作的情況:

  • ContentProvider#onCreate() 中將 Context 轉換為 Application 類別時,這個方法會在應用程式啟動期間呼叫,我們還沒有機會替換 Application 類別的執行個體,因此 ContentProvider 仍會參照虛設常式應用程式,而非實際應用程式。從某種意義上來說,這並非錯誤,因為您不應以這種方式向下轉換 Context,但 Google 的幾個應用程式似乎會發生這種情況。

  • bazel mobile-install 安裝的資源只能在應用程式內使用。如果其他應用程式透過 PackageManager#getApplicationResources() 存取資源,這些資源會來自上次非累加式安裝。

  • 未執行 ART 的裝置。雖然 Stub 應用程式在 Froyo 以上版本運作良好,但 Dalvik 有個錯誤,在某些情況下,如果應用程式的程式碼分散在多個 .dex 檔案中,就會認為應用程式不正確,例如以特定方式使用 Java 註解時。只要應用程式不會觸發這些錯誤,應該也能與 Dalvik 搭配運作 (但請注意,我們並非特別著重於支援舊版 Android)