このドキュメントでは、問題を診断して、ベースライン プロファイルが正しく機能し、最大限のメリットをもたらすようにするためのおすすめの方法を紹介します。
ビルドの問題
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 で [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
を Firebase 向け Google アナリティクスなどの分析レポート ライブラリと組み合わせて使用することで、プロファイルのステータスを示す分析イベントを生成できます。たとえば、ベースライン プロファイルを含まない新しいアプリ バージョンがリリースされた場合は、すぐに通知されます。
ベースライン プロファイルのコンパイルを強制的に行う
ベースライン プロファイルのコンパイル ステータスが 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]
status 値はプロファイルのコンパイル ステータスを示します。以下のいずれかの値になります。
コンパイル ステータス | 意味 |
---|---|
speed‑profile |
コンパイル済みのプロファイルがあり、使用されている。 |
verify |
コンパイル済みのプロファイルがない。 |
verify
ステータスは、APK または AAB にプロファイルが含まれていないという意味ではありません。この場合は、プロファイルは次回のバックグラウンド DEX 最適化タスクでコンパイルのキューに追加されます。
reason 値は、プロファイルがコンパイルされた状況を示します。以下のいずれかの値になります。
理由 | 意味 |
---|---|
install‑dm
|
ベースライン プロファイルがアプリのインストール時に手動または Google Play によってコンパイルされた。 |
bg‑dexopt
|
デバイスがアイドル状態のときにプロファイルがコンパイルされた。これは、ベースライン プロファイルの場合もあれば、アプリの使用中に収集されたプロファイルの場合もあります。 |
cmdline
|
コンパイルは adb を使用してトリガーされた。これは、ベースライン プロファイルの場合もあれば、アプリの使用中に収集されたプロファイルの場合もあります。 |
パフォーマンスの問題
このセクションでは、ベースライン プロファイルを正しく定義してベンチマークを実行し、そのメリットを最大限に引き出すためのおすすめの方法について説明します。
起動時の指標のベンチマークを正しく実行する
起動時の指標が明確に定義されていれば、ベースライン プロファイルの効果は高まります。主な 2 つの指標は、初期表示までの時間(TTID)と完全表示までの時間(TTFD)です。
TTID はアプリが最初のフレームを描画する時点を指します。何かを表示するとは、アプリが実行されていることをユーザーに示すことであるため、可能な限り時間がかからないようにすることが重要です。アプリが応答していることを示す不確定形式の進行状況インジケーターを表示する方法もあります。
TTFD はアプリを実際に操作できるようになった時点を指します。ユーザーがフラストレーションを感じないよう、できる限り時間がかからないようにすることが重要です。TTFD を正しく通知すれば、TTFD までの間に実行されるコードがアプリの起動に関連するものであることをシステムに伝えることになり、結果として、そのコードがプロファイルに入る可能性が高くなります。
TTID と TTFD のどちらも可能な限り短くして、アプリが応答していると感じられるようにしてください。
システムは TTID を検出して Logcat に表示し、起動ベンチマークの一部として報告することはできますが、TTFD を判断することはできません。完全に描画された操作可能な状態に達したら、アプリが報告する必要があります。このためには、reportFullyDrawn()
を呼び出すか、Jetpack Compose を使用している場合は ReportDrawn
を呼び出します。アプリが完全に描画されたと見なされるまでにすべて完了する必要がある複数のバックグラウンド タスクがある場合は、FullyDrawnReporter
を使用できます。これについては、起動時間の精度を改善するをご覧ください。
ライブラリ プロファイルとカスタム プロファイル
プロファイルの影響を評価する場合、 次のような、ライブラリから提供されたプロファイルから、アプリ プロファイルのメリットを享受できます。 Jetpack ライブラリ。APK をビルドすると、Android Gradle プラグインによって カスタム プロファイルも作成する必要があります。OK を使用することをおすすめします。リリースビルドで使用することをおすすめします。 ただし、これによりパフォーマンスがどの程度向上するかを測定するのは困難になります。 カスタムプロファイルから作成できます
カスタム ディメンションによる追加の最適化を プロファイルを削除し、ベンチマークを実行します。置き換えて実行します。 再挑戦できます。2 つを比較することで、Google Cloud で提供されている ライブラリ プロファイルのみ、ライブラリ プロファイル + カスタム プロファイルの 3 つのプロパティがあります。
プロファイル比較を自動化可能な方法として、新しいビルド バリアントを作成することで、
ライブラリ プロファイルのみが含まれ、カスタム プロファイルは含まれません。比較
ベンチマークを、このバリアントから、
カスタム プロファイルを作成します。次の例は
ライブラリ プロファイルのみを含むバリアントをセットアップします。新しいバリエーションを追加
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 でベンチマークを実行するときの実装。
重要なユーザー ジャーニーをすべてカバーする
重要なユーザー ジャーニーはすべて、ベースライン プロファイルの生成で正確にカバーすることが大切です。カバーされていないユーザー ジャーニーは、ベースライン プロファイルで改善されません。一般的な起動のユーザー ジャーニーとパフォーマンス重視のアプリ内ユーザー ジャーニー(リストのスクロールなど)を含むベースライン プロファイルが、最も効果的です。