本文說明診斷問題及確保基準設定檔正確運作的最佳做法,幫助您發揮基準設定檔的最大效益。
版本問題
如果您已複製 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」,在模擬器上啟用基準設定檔:
安裝問題
請確認您建構的 APK 或 AAB 來自包含基準設定檔的建構變化版本。最簡單的確認方法,就是在 Android Studio 中開啟 APK。請依序選取「Build」>「Analyze APK」、開啟 APK,然後找出 /assets/dexopt/baseline.prof
檔案中的設定檔:
基準設定檔必須在執行應用程式的裝置上編譯。無論是應用程式商店安裝作業,還是透過 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 中進行基準測試時的實作。
涵蓋所有重要使用者歷程
請務必準確涵蓋產生基準設定檔時的所有重要使用者歷程。基準設定檔不會改善未涵蓋的使用者歷程。最有效的基準設定檔包含所有常見的啟動使用者歷程,以及易受效能影響的應用程式內使用者歷程,例如捲動清單。