基準設定檔

基準設定檔是一份由不同類別和方法組成的清單,包含在 APK 中供 Android 執行階段 (ART) 安裝期間使用,以便將重要路徑預先編入機器碼。這是設定檔引導最佳化 (PGO) 的一種形式,可協助應用程式最佳化啟動作業、減少資源浪費,及改善終端使用效能。

基準設定檔的運作方式

設定檔規則會以二進位格式編譯到 APK 內,位於 assets/dexopt/baseline.prof

在安裝期間,ART 會預先 (AOT) 編譯設定檔中的方法,加快它們的執行速度。如果設定檔中包含應用程式啟動或進行畫面轉譯時使用的方法,使用者將能感受到程式啟動變快且/或卡頓狀況減少。

開發應用程式或程式庫時,我們建議規範基準設定檔以涵蓋關鍵使用者旅程中的特定熱門路徑。因為對於這些路徑,例如程式啟動、系統轉換和頁面捲動,轉譯時間和延遲時間長短是很重要的。

這樣一來,就能透過 Google Play 將基準設定檔與 APK 一併直接提供給使用者,如圖 1 所示:

圖 1. 這張圖表展示從上傳資料到最終發布之間的基準設定檔工作流程,以及該工作流程與雲端設定檔之間的關係。

使用基準設定檔的原因

應用程式的啟動時間是提升使用者參與度的關注重點。提升啟動效率和回應速度除了會增加每日活躍使用者數,還能夠提高平均回訪率。

雲端設定檔也會最佳化上述提到的互動功能,但發布更新後,使用者至少要再等一天才能體驗到最佳化效果。此外,這項服務也不支援 Android 7 (API 24) 至 Android 8 (API 26) 版本。

各版 Android 的編譯行為

各版 Android 平台採用不同的應用程式編譯方式,也各有各的優缺點。基準設定檔藉由為所有已安裝項目提供資料,改善先前的編譯方法。

Android 版本 編譯方法 最佳化方法
Android 5 (API 層級 21) 至 Android 6 (API 層級 23) 完整 AOT 整個應用程式在安裝作業期間都會進行最佳化,因此會造成長時間等候使用應用程式、RAM 和磁碟空間使用量增加,以及從磁碟載入程式碼的時間拉長等狀況,甚至冷啟動時間也可能會變長。
Android 7 (API 層級 24) 至 Android 8.1 (API 層級 27) 部分 AOT (基準設定檔) 第一次執行應用程式時,系統會透過 androidx.profileinstaller 安裝基準設定檔,那時這個依附元件是由程式模組定義。當裝置處於閒置狀態時,ART 可能會透過新增額外的設定檔規則並予以編譯,進一步改善上述規範。這樣可以大幅改善磁碟空間並減少從磁碟載入程式碼的時間,進而縮短應用程式的等待時間。
Android 9 (API 層級 28) 以上版本 部分 AOT (基準 + 雲端設定檔) Google Play 會在應用程式安裝期間使用基準設定檔,最佳化 APK 和雲端設定檔 (如果有的話)。安裝完畢後,ART 設定檔會上傳至 Google Play 並進行匯總,然後在其他使用者安裝/更新應用程式時,以雲端設定檔的形式提供。

建立基準設定檔

利用 BaselineProfileRule 自動建立設定檔規則

作為應用程式開發人員,您可以使用 Jetpack Macrobenchmark 程式庫,在每次應用程式發布時自動產生設定檔。

以下說明如何用 Macrobenchmark 程式庫建立基準設定檔:

  1. 請在應用程式 build.gradleProfileInstaller 程式庫新增依附元件,以便啟用本地和 Play 商店基準設定檔編譯。這是在本機側載基準設定檔的唯一方法。

    dependencies {
         implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01")
    }
    
  2. 在 Gradle 專案中設定 Macrobenchmark 模組

  3. 定義名為 BaselineProfileGenerator 的新測試,如下:

    @ExperimentalBaselineProfilesApi
    @RunWith(AndroidJUnit4::class)
    class BaselineProfileGenerator {
        @get:Rule val baselineProfileRule = BaselineProfileRule()
    
        @Test
        fun startup() =
            baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {
                pressHome()
                // This block defines the app's critical user journey. Here we are interested in
                // optimizing for app startup. But you can also navigate and scroll
                // through your most important UI.
                startActivityAndWait()
            }
    }
    
  4. 連接執行 Android 9 以上版本的 userdebug 或已啟用 Root 權限的 Android 開放原始碼計畫 (AOSP) 模擬器。

  5. 在終端機執行 adb root 指令,確認 ADB Daemon 正在執行且有 Root 權限。

  6. 執行測試並等待測試完畢。

  7. 到 Logcat 找出產生設定檔的位置。搜尋記錄標記 Benchmark

    com.example.app D/Benchmark: Usable output directory: /storage/emulated/0/Android/media/com.example.app
    
    # List the output baseline profile
    ls /storage/emulated/0/Android/media/com.example.app
    SampleStartupBenchmark_startup-baseline-prof.txt
    
  8. 在裝置上開啟產生的檔案。

    adb pull storage/emulated/0/Android/media/com.example.app/SampleStartupBenchmark_startup-baseline-prof.txt .
    
  9. 將產生的檔案重新命名為 baseline-prof.txt,並複製到應用程式模組的 src/main 目錄。

手動定義設定檔規則

您可以透過在 src/main 目錄中建立名為 baseline-prof.txt 的檔案,藉此手動定義應用程式或程式庫模組中的設定檔規則。上述資料夾與存有 AndroidManifest.xml 檔案的是同一個。

在這個檔案中,一行指定一個規則。每個規則都代表一種特定模式,其中每個模式都包含應用程式或程式庫中同樣需要最佳化的對應方法或類別。

使用 adb shell profman --dump-classes-and-methods 時,這些規則的語法算是一個人類可讀的 ART 設定檔格式 (HRF) 超集。該語法和描述元及簽名的語法非常類似,但前者同時也允許使用萬用字元來簡化規則寫入程序。

下列範例列出了包含在 Jeetpack Compose 程式庫中的幾個基準設定檔規則:

HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Landroidx/compose/runtime/ComposerImpl;

規則語法

這些規則能以兩種模式之一來指定方法或類別:

[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]

類別規則使用以下模式:

[CLASS_DESCRIPTOR]
語法 說明
FLAGS 代表 HS,和 P 的一或多個字元,點出此方法應標記為 HotStartup,或 Post Startup,與啟動類型相關。

具有 H 標記表示這個方法很「熱門」,也就是系統會在應用程式生命週期間多次呼叫的方法。

具有 S 標記表示系統會在應用程式啟動時呼叫這個方法。

具有 P 標記表示這個方法是與程式啟用無關的熱門方法。

存在於這個檔案中,代表這個類別會在程式啟動時被選用,應在堆積中預先分配以減少類別載入時的消耗。ART 編譯器採用多樣的最佳化策略,例如上述的 AOT 編譯方法,以及最佳化 先前產生的 AOT 檔案的版面配置。
CLASS_DESCRIPTOR 指定方法類別的技術子。舉例來說,androidx.compose.runtime.SlotTable 的記述子是 Landroidx/compose/runtime/SlotTable;。注意:系統會依據Dalvik Executable (DEX) 格式在開頭加上 L 字。
METHOD_SIGNATURE 方法的簽署,包括方法的名稱、參數類型和傳回類型。舉例來說,LayoutNode 上的方法

// LayoutNode.kt

fun isPlaced():Boolean {
// ...
}

具有 isPlaced()Z 簽署。

這些模式可利用萬用字元,以便讓單一規則包含多種方法或類別。如果在使用 Android Studio 的規則語法編寫時需要引導或協助,請參考 Android 基準設定檔外掛程式。

萬用字元規則的範例如下:

HSPLandroidx/compose/ui/layout/**->**(**)**

基準設定檔規則的支援類型

基準設定檔規則支援下列類型。想進一步瞭解這些類型,請參考 Dalvik Executable (DEX) 格式

字元 類型 說明
B 位元組 位元組 (已簽署)
C 字元 編入 UTF-16 的統一碼字元碼位
D 雙倍 雙精度浮點數
F 浮動 單精度浮點數
I int 整數
J long long int
S short signed short
V void void
Z 布林 對或錯
L (類別名稱) 參考資料 類別名稱的例子

此外,程式庫也可以規範即將封裝於 AAR 構件中的規則。當您建構 APK 以納入這些構件時,系統會將規則合併在一起 (類似於資訊清單的合併方式),並編譯為 APK 專用的精簡二進位 ART 設定檔。

當 Android 9 (API 級別 28) 版本的裝置,在安裝期間使用 APK 預先編譯應用程式的特定子集時,ART 就能善用這個設定檔。而 Android 7 (API 級別 24) 版本的裝置則是在使用 ProfileInstaller 時,讓 ART 善用這個設定檔。

其他附註

建立基準設定檔時,請注意以下額外幾點:

  • Android 5 到 Android 6 (API 級別 21 和 23) 會在安裝時預先編譯 APK。

  • 我們不會預先編譯可偵錯的應用程式,以便協助排解問題。

  • 規則檔案必須命名為 baseline-prof.txt,並置於主要來源集的根目錄中 (應該要是 AndroidManifest.xml 檔案的一個子檔案)。

  • 您需要使用 Android Gradle 外掛程式 7.1.0-alpha05 以上的版本 (Android Studio Bumblebee Canary 5) 才能利用這些檔案。

  • Bazel 目前尚不支援讀取基準設定檔,或將其合併成 APK。

  • 基準設定檔壓縮後不得超過 1.5 MB。因此程式庫和應用程式應盡力規範出擁有足夠功能,但較少的檔案規則。

  • 如果規則編得過於複雜,應用程式會因為磁碟存取量變大而降低啟動速度。我們在此建議您測試基準設定檔的效能。

評估改善空間

利用 Macrobenchmark 程式庫進行自動化評估

Macrobenchmark 可讓您透過 CompilationMode API (包括 BaselineProfile 的使用) 來控制預先評估。

如果您已在 Macrobenchmark 模組中設定 BaselineProfileRule 測試,則可在該模組中定義一項新測試來評估效能:

@RunWith(AndroidJUnit4::class)
class BaselineProfileBenchmark {
  @get:Rule
  val benchmarkRule = MacrobenchmarkRule()

  @Test
  fun startupNoCompilation() {
    startup(CompilationMode.None())
  }

  @Test
  fun startupBaselineProfile() {
    startup(CompilationMode.Partial(
      baselineProfileMode = BaselineProfileMode.Require
    ))
  }

  private fun startup(compilationMode: CompilationMode) {
    benchmarkRule.measureRepeated(
      packageName = "com.example.app",
      metrics = listOf(StartupTimingMetric()),
      iterations = 10,
      startupMode = StartupMode.COLD,
      compilationMode = compilationMode
    ) { // this = MacrobenchmarkScope
        pressHome()
        startActivityAndWait()
    }
  }
}

圖 2 提供傳遞測試結果的範例:

圖 2. 這是小型測試的結果。更大型的應用程式可以透過基準設定檔獲得更多優勢。

請注意,雖然上方範例關注的是 StartupTimingMetric,但仍有其他重要指標需要評估,例如可以使用 Jetpack Macrobenchmark 進行衡量的卡頓情形 (畫面指標)

手動評估應用程式改善情形

首先,來評估尚未最佳化的應用程式啟動作為對照組。

PACKAGE_NAME=com.example.app
# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Reset compiled state
adb shell cmd package compile --reset $PACKAGE_NAME
# Measure App startup
# This corresponds to `Time to initial display` metric
# For additional info https://developer.android.com/topic/performance/vitals/launch-time#time-initial
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

接著,側載基準設定檔。

# Unzip the Release APK first
unzip release.apk
# Create a ZIP archive
# Note: The name should match the name of the APK
# Note: Copy baseline.prof{m} and rename it to primary.prof{m}
cp assets/dexopt/baseline.prof primary.prof
cp assets/dexopt/baseline.profm primary.profm
# Create an archive
zip -r release.dm primary.prof primary.profm
# Confirm that release.dm only contains the two profile files:
unzip -l release.dm
# Archive:  release.dm
#   Length      Date    Time    Name
# ---------  ---------- -----   ----
#      3885  1980-12-31 17:01   primary.prof
#      1024  1980-12-31 17:01   primary.profm
# ---------                     -------
#                               2 files
# Install APK + Profile together
adb install-multiple release.apk release.dm

為確認套件在安裝時是否已完成最佳化,請執行下列指令:

# Check dexopt state
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME

輸出結果應顯示套件已完成編譯。

[com.example.app]
  path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/com.example.app-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
  arm64: [status=speed-profile] [reason=install-dm]

現在,可以像之前一樣評估應用程式啟動效能,而且不會影響已編譯的狀態。

# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Measure App startup
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

已知問題

目前使用基準設定檔會發生幾項已知的問題:

  • 如果使用應用程式套件建構 APK,會發生基準設定檔無法正確封裝的問題。如果要解決此問題,請套用 com.android.tools.build:gradle:7.3.0-beta02 以上版本 (問題)。

  • 只有主要的 classes.dex 檔案才能正確封裝基準設定檔。這個問題會影響到有超過一個 .dex 檔案的應用程式。如果要解決此問題,請套用 com.android.tools.build:gradle:7.3.0-beta02 以上版本 (問題)。

  • Macrobenchmark 在 Android 12L (API 32) (問題) 和 Android 13 (API 33) (問題) 無法和基準設定檔相容。

  • user (未啟用 Root 權限) 版本不得重設 ART 設定檔快取。androidx.benchmark:benchmark-macro-junit4:1.1.0-rc02 內含可以在基準測試期間重新安裝應用程式的修復方式,可以解決此問題 (問題)。

  • Android Studio 分析器在分析應用程式時不會安裝基準設定檔 (問題)。

  • 非 Gradle 建構系統 (Bazel、Buck 等) 目前不支援將基準設定檔編譯到輸出的 APK 中。

  • Play 商店目前在 AAB 上傳後會需要 10 到 14 個小時,才能在安裝時提供基準設定檔。如果使用者在這段期間內下載應用程式,必須等到 dexopt 在背景執行 (可能隔夜) 後才會看到相關優勢。我們正在積極改進這一點。

  • 非 Play 商店應用程式發布管道可能不支援在安裝時使用基準設定檔。透過這些管道取得應用程式的使用者,必須等到 dexopt 在背景執行 (可能隔夜) 後才會看到相關優勢。