bazel 行動安裝

回報問題 查看原始碼 Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

快速疊代 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 應用程式時會發生一些問題,包括:

  • Dex 處理。根據預設,dx 在建構作業中只會叫用一次,而且不知道如何重複使用先前建構中的工作:即使只有變更一個方法,也會重新執行所有方法。

  • 將資料上傳至裝置。adb 不會使用 USB 2.0 連線的完整頻寬,因此較大的應用程式可能需要花費大量時間上傳。即使只有部分內容 (例如資源或單一方法) 發生變更,整個應用程式也會上傳,因此這可能會造成重大瓶頸。

  • 編譯為原生程式碼。Android L 推出了 ART,這是新的 Android 執行階段,會預先編譯應用程式,而非像 Dalvik 一樣在適當時機編譯。這樣可以加快應用程式的速度,但安裝時間較長。這是對使用者而言有利的取捨,因為使用者通常只安裝應用程式一次,且使用多次,但會導致開發速度變慢,因為使用者安裝應用程式數次,每個版本的執行次數最多。

bazel mobile-install 的做法

bazel mobile-install 改善了以下項目:

  • 資料分割 dex 處理。建構應用程式的 Java 程式碼後,Bazel 會將類別檔案切割成大致相等大小的部分,並分別在這些部分上叫用 dx。系統不會在上次建構後未變更的資料分割上叫用 dx

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

  • 從 .apk 外部載入應用程式的某些部分。一個小型的虛設常式應用程式位於 .apk 中,這個檔案會從裝置端的行動裝置安裝目錄載入 Android 資源、Java 程式碼和原生程式碼,然後將控制權轉移到實際應用程式。這會對應用程式公開,但以下幾個極端情況除外。

分割 Dex 處理

資料分割的 dex 相當簡單明瞭:建立 .jar 檔案後,工具會將這些檔案分割成大小相等大小相同的個別 .jar 檔案,然後針對自上次建構後變更的 .jar 檔案叫用 dx。決定要將哪些分片進行 dex 的邏輯並非 Android 專屬,而是使用 Bazel 的一般變更修剪演算法。

資料分割演算法的第一個版本只會按照字母順序排序 .class 檔案,然後將清單切割為大小相等的部分,但成效不佳:如果新增或移除類別 (即使是巢狀或匿名類別),則所有類別在開始位移後都會按字母順序排列,導致所有類別再次解開這些資料分割。因此,我們決定將 Java 套件分割,而非個別類別。當然,如果新增或移除新套件,這仍會導致系統為許多分片進行解析,但這比新增或移除單一類別的頻率低得多。

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

增量檔案傳輸

應用程式建構完畢後,下一步最好以最省時的方式進行安裝。安裝程序包含下列步驟:

  1. 安裝 .apk (通常使用 adb install)
  2. 將 .dex 檔案、Android 資源和原生資料庫上傳至行動裝置安裝目錄

第一步驟的增量不大:應用程式要嘛已安裝,要嘛未安裝。Bazel 目前取決於使用者是否應透過 --incremental 指令列選項執行這個步驟,因為在所有情況下都無法判斷。

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

請注意,您可以透過變更裝置上的檔案來欺騙增量安裝演算法,但無法變更資訊清單中的總和檢查碼。這個功能可以計算裝置上的檔案總和檢查碼,藉此防範上述問題,但我們認為這種情況下的安裝時間不會增加。

Stub 應用程式

在 Stub 應用程式中,會執行從裝置端 mobile-install 目錄載入 dex、原生程式碼和 Android 資源的魔法。

實際載入是將 BaseDexClassLoader 設為子類別,可以採取合理記錄技術的做法。這個情形會在應用程式的任何類別載入前進行,因此,APK 中的所有應用程式類別都可以放在裝置的 mobile-install 目錄中,以便在沒有 adb install 的情況下更新。

這項操作必須在載入應用程式的任何類別之前完成,因此不需要在 .apk 中加入任何應用程式類別,否則變更這些類別就必須完全重新安裝。

方法是將 AndroidManifest.xml 中指定的 Application 類別,替換為虛設應用程式。這樣可控制應用程式的啟動時間,並在最先時刻 (其建構函式) 使用 Android 架構內部的 Java 反射來適當調整類別載入器和資源管理員。

虛設常式應用程式還有一項功能,就是將行動裝置安裝時安裝的原生資料庫複製到其他位置。此為必要步驟,因為在檔案上設定動態連接器需要 X 位元,這對於非根 adb 存取的任何位置都無法執行此操作。

完成所有這些操作後,輔助程式就會將實際的 Application 類別例項化,將所有對自身的參照變更為 Android 架構中的實際應用程式。

結果

成效

一般來說,只要稍做調整,bazel mobile-install 就能加快建構和安裝大型應用程式的 4 至 10 倍速度。

我們針對幾項 Google 產品計算出下列數字:

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

限制

因此,即使是用於測試的應用程式,也無法在所有情況下運作。以下幾種情況會導致不如預期:

  • Context 轉換為 ContentProvider#onCreate() 中的 Application 類別時。這個方法會在應用程式啟動期間呼叫,因此我們無法更換 Application 類別的例項,因此 ContentProvider 仍會參照 Stub 應用程式,而非實際應用程式。這並非錯誤,因為您不應以這種方式下放 Context,但 Google 的部分應用程式似乎發生了這種情況。

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

  • 未執行 ART 的裝置。雖然虛設常式應用程式可以在 Froyo 和以上版本中順利運作,但 Dalvik 有一項錯誤,導致如果應用程式的程式碼在特定情況下 (例如透過特定方式使用 Java 註解) 發布至多個 .dex 檔案,就會認為應用程式不正確。只要您的應用程式不會觸發這些錯誤,就應該也能與 Dalvik 搭配運作 (但請注意,我們並未特別著重於支援舊版 Android)