對基準設定檔進行偵錯

本文說明診斷問題及確保基準設定檔正確運作的最佳做法,幫助您發揮基準設定檔的最大效益。

版本問題

如果您已複製 Now in Android 範例應用程式中的基準設定檔,可能會在基準設定檔工作期間遇到測試失敗情形,指出測試無法在模擬器上執行:

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

失敗原因是 Now in Android 採用 Gradle 管理的裝置產生基準設定檔。這種失敗是正常現象,因為您通常不應在模擬器上執行效能基準測試。不過,您不會在產生基準設定檔時收集效能指標,所以為了方便起見,可以在模擬器上執行基準設定檔收集作業。如要搭配使用基準設定檔和模擬器,請透過指令列執行建構和安裝作業,並設定引數來啟用基準設定檔規則:

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

或者,您也可以在 Android Studio 中建立自訂執行設定,然後依序選取「Run」>「Edit Configurations」,在模擬器上啟用基準設定檔:

新增自訂執行設定,在 Now in Android 中建立基準設定檔
圖 1. 新增自訂執行設定,在 Now in Android 中建立基準設定檔。

安裝問題

請確認您建構的 APK 或 AAB 來自包含基準設定檔的建構變化版本。最簡單的確認方法,就是在 Android Studio 中開啟 APK。請依序選取「Build」>「Analyze APK」、開啟 APK,然後找出 /assets/dexopt/baseline.prof 檔案中的設定檔:

使用 Android Studio 中的 APK 檢視器,檢查基準設定檔
圖 2. 使用 Android Studio 中的 APK 檢視器,檢查基準設定檔。

基準設定檔必須在執行應用程式的裝置上編譯。無論是應用程式商店安裝作業,還是透過 PackageInstaller 安裝的應用程式,系統都會在應用程式安裝程序期間執行裝置端編譯作業。但如果應用程式是從 Android Studio 側載,或是使用指令列工具,Jetpack ProfileInstaller 程式庫會負責在下次背景 DEX 最佳化程序中,將設定檔排入編譯佇列。在這種情況下,如要確保使用基準設定檔,可能需要強制編譯基準設定檔。您可以使用 ProfileVerifier 查詢設定檔的安裝和編譯狀態,如以下範例所示:

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java

public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

下列結果代碼可提示部分問題的原因:

RESULT_CODE_COMPILED_WITH_PROFILE
每當應用程式執行時,系統都會安裝、編譯並使用設定檔。這是您樂見的結果。
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
在執行的 APK 或 AAB 中找不到設定檔。如果看到這項錯誤,但 APK 內含設定檔,請確認您使用的是包含基準設定檔的建構變化版本。
RESULT_CODE_NO_PROFILE
透過應用程式商店或套件管理員安裝應用程式時,未安裝此應用程式的設定檔。出現此錯誤代碼的主因是 ProfileInstallerInitializer 已停用,因此設定檔安裝程式並未執行。請注意,系統回報此錯誤時,仍會在應用程式 APK 中找到嵌入的設定檔。如果找不到嵌入的設定檔,則傳回的錯誤代碼為 RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
設定檔位於 APK 或 AAB 中,且已排入編譯佇列。若是由 ProfileInstaller 安裝設定檔,系統會在下次執行背景 DEX 最佳化工作時,將設定檔排入編譯佇列。編譯完成後,設定檔才會生效。在編譯完成前,切勿嘗試對基準設定檔執行基準測試。您可能需要強制編譯基準設定檔。在搭載 Android 9 (API 28) 以上版本的裝置上,如果應用程式是透過應用程式商店或套件管理員安裝,就不會發生這項錯誤,因為系統會在安裝期間執行編譯作業。
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
安裝的設定檔不相符,而系統已使用該設定檔編譯應用程式。這是透過 Google Play 商店或套件管理員完成安裝的結果。請注意,這項結果與 RESULT_CODE_COMPILED_WITH_PROFILE 不同,因為不相符的設定檔只會編譯設定檔和應用程式之間仍共用的方法。實際上,設定檔會小於預期,編譯的方法也會比基準設定檔更少。
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier 無法寫入驗證結果快取檔案。這可能是因為應用程式資料夾權限有誤,或是裝置可用磁碟空間不足。
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifier is running on an unsupported API version of Android. ProfileVerifier 只支援 Android 9 (API 級別 28) 以上版本。
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
查詢應用程式套件的 PackageManager 時,系統擲回 PackageManager.NameNotFoundException。這種情況應該很少發生。請嘗試解除安裝應用程式,並重新安裝所有項目。
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
已有先前的驗證結果快取檔案,但無法讀取。這種情況應該很少發生。請嘗試解除安裝應用程式,並重新安裝所有項目。

在實際工作環境中使用 ProfileVerifier

在實際工作環境中,您可以搭配使用 ProfileVerifier 與數據分析報表程式庫 (例如 Google Analytics for Firebase),產生指出設定檔狀態的數據分析事件。舉例來說,當新推出的應用程式版本不含基準設定檔時,您就能迅速收到快訊。

強制編譯基準設定檔

如果基準設定檔的編譯狀態為 RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION,您可以使用 adb 強制立即編譯:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

不使用 ProfileVerifier 檢查編譯狀態

如果不使用 ProfileVerifier,可以運用 adb 檢查編譯狀態,但這無法像使用 ProfileVerifier 一樣取得深入分析資料:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

使用 adb 產生的內容會類似如下:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

狀態值代表設定檔編譯狀態,且會是下列其中一個值:

編譯狀態 意義
speed‑profile 含有已編譯的設定檔,且正在使用該設定檔。
verify 沒有已編譯的設定檔。

verify 狀態並非代表 APK 或 AAB 不包含設定檔,因為下一個背景 DEX 最佳化工作可能會將設定檔排入編譯佇列。

原因值可以是下列其中一個值,可指出觸發設定檔編譯作業的原因:

原因 意義
install‑dm 基準設定檔是在應用程式安裝時手動編譯,或由 Google Play 編譯。
bg‑dexopt 系統是在裝置處於閒置狀態時編譯設定檔。該設定檔可能是基準設定檔,也可能是應用程式使用期間收集的設定檔。
cmdline 編譯作業是由 ADB 觸發。該設定檔可能是基準設定檔,也可能是應用程式使用期間收集的設定檔。

效能問題

本節介紹一些最佳做法,說明如何對基準設定檔設下正確定義、執行基準測試,進而發揮基準設定檔的最大效益。

正確執行啟動指標的基準測試

如果啟動指標的定義明確,基準設定檔將更有效率。兩項關鍵指標分別是初始顯示時間 (TTID)完整顯示時間 (TTFD)

TTID 是指應用程式繪製第一個影格的時間。請務必盡量縮短 TTID,因為使用者看到內容後,就會知道應用程式正在執行。您甚至可以顯示未確定的進度指標,表示應用程式有回應。

TTFD 是指使用者能實際與應用程式互動的時間。請務必盡量縮短 TTFD,以免使用者感到不悅。如果正確發出 TTFD 信號,即表示在 TTFD 前執行的程式碼,皆屬於應用程式啟動程序。如此一來,系統就較可能將這類程式碼放入設定檔。

請盡量降低 TTID 和 TTFD,讓使用者覺得應用程式有所回應。

系統可以偵測 TTID、將 TTID 顯示在 Logcat 中,並回報為啟動基準測試的一部分,但系統無法判定 TTFD。在完整繪製出可供互動的畫面前,須由應用程式負責回報 TTFD。您可以呼叫 reportFullyDrawn() 來回報這項資訊。如果使用 Jetpack Compose,則可呼叫 ReportDrawn。如有許多需要完成的背景工作,而且這些工作必須在應用程式完全繪製前完成,可以使用 FullyDrawnReporter,詳情請參閱「提高啟動時間準確度」。

程式庫設定檔和自訂設定檔

對設定檔的影響執行基準測試時,很難區分 從程式庫提供的設定檔中找出應用程式設定檔的優點,例如 Jetpack 程式庫。當您建構 APK 時,Android Gradle 外掛程式會新增 以及自訂設定檔中的設定檔不錯 ,以提升整體效能,不適用於發布子版本。 然而,很難評估在 2023 年 從您的自訂設定檔中取而代之。

快速手動查看自訂額外最佳化項目 並執行基準測試然後更換並執行 再次執行基準測試比較兩者後,您會看到 程式庫設定檔、圖書館設定檔和您的自訂設定檔。

要自動比較剖析資料,方法是建立新的建構變數。 僅包含圖書館設定檔,而非您的自訂設定檔。比較 從此變化版本到發布變化版本的基準測試,同時包含 程式庫設定檔和自訂設定檔以下範例說明 即可設定只包含程式庫設定檔的變化版本。新增變化版本 名為 releaseWithoutCustomProfile 的設定檔取用端模組 通常是應用程式模組:

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

上述程式碼範例會將 baselineProfile 依附元件從 ,並視需要將其套用至 release 變數。可能會 程式庫設定檔在系統維護期間 移除設定檔產生者模組的依附元件。不過本單元 您完全不需要自行產生自訂設定檔Android Gradle 外掛程式仍會在所有變化版本中執行,並會負責將 程式庫設定檔

您也必須將新的變化版本新增至設定檔產生器模組。在本 例如,生產端模組的名稱為 :baselineprofile

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

透過 Android Studio 執行基準測試時,請選取 releaseWithoutCustomProfile 個變化版本,用來僅利用程式庫評估成效 設定檔,或選取 release 變化版本,以便使用程式庫評估成效 以及自訂設定檔

避免 I/O 密集型應用程式啟動程序

如果應用程式在啟動期間執行大量 I/O 呼叫或網路呼叫,可能會對應用程式啟動時間和啟動基準測試的準確度造成負面影響。這類高負載呼叫所需的處理時間並不一定,不僅可能隨時間變化,甚至在同一基準測試的疊代間都可能不同。I/O 呼叫通常優於網路呼叫,因為網路呼叫可能會受裝置外部因素和裝置本身的影響。請避免在啟動期間發出網路呼叫。如果必須發出呼叫,請使用 I/O 呼叫。

您採用的應用程式架構應能在不發出網路呼叫或 I/O 呼叫的情況下啟動應用程式,即使只會在基準測試啟動期間使用這項架構也一樣。這樣一來,就能盡量降低不同基準測試疊代間產生差異的可能性。

如果您的應用程式使用 Hilt,您可以提供虛假的 I/O 邊界 在 Microbenchmark 和 Hilt 中進行基準測試時的實作。

涵蓋所有重要使用者歷程

請務必準確涵蓋產生基準設定檔時的所有重要使用者歷程。基準設定檔不會改善未涵蓋的使用者歷程。最有效的基準設定檔包含所有常見的啟動使用者歷程,以及易受效能影響的應用程式內使用者歷程,例如捲動清單。