В этом документе представлены передовые методы диагностики проблем и обеспечения корректной работы базовых профилей для получения максимальной пользы.
Проблемы сборки
Если вы скопировали пример базовых профилей из примера приложения 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 в Android использует устройство, управляемое Gradle, для генерации базовых профилей. Сбои ожидаемы, поскольку обычно не следует запускать тесты производительности на эмуляторе. Однако, поскольку вы не собираете показатели производительности при генерации базовых профилей, вы можете запустить сбор базовых профилей на эмуляторах для удобства. Чтобы использовать базовые профили с эмулятором, выполните сборку и установку из командной строки и задайте аргумент для включения правил базовых профилей:
installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
Кроме того, вы можете создать пользовательскую конфигурацию запуска в Android Studio, чтобы включить базовые профили на эмуляторах, выбрав «Запуск» > «Изменить конфигурации» :

Проблемы с установкой
Убедитесь, что проверяемый вами APK или AAB относится к варианту сборки, включающему базовые профили:
- В Android Studio выберите «Сборка» > «Анализ APK» .
- Откройте ваш AAB или APK.
- Если вы проверяете AAB, профиль находится в
/BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof
. Если вы проверяете APK, профиль находится в/assets/dexopt/baseline.prof
.

Базовые профили необходимо скомпилировать на устройстве, на котором запущено приложение. При установке приложения с помощью Play Store, Android Studio или инструмента командной строки Gradle Wrapper компиляция на устройстве происходит автоматически. При установке приложения с помощью других инструментов библиотека Jetpack ProfileInstaller
отвечает за постановку профилей в очередь для компиляции во время следующего фонового процесса оптимизации DEX. В этих случаях, если вы хотите убедиться, что ваши базовые профили используются, вам может потребоваться принудительная компиляция базовых профилей . ProfileVerifier
позволяет вам запрашивать статус установки и компиляции профиля, как показано в следующем примере:
Котлин
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") } } }
Ява
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. Убедитесь, что вы используете вариант сборки, включающий базовые профили, если видите эту ошибку, и что 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 для 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 — это когда приложение рисует свой первый кадр. Важно сделать его как можно короче, поскольку отображение чего-либо показывает пользователю, что приложение запущено. Вы даже можете отобразить неопределенный индикатор прогресса, чтобы показать, что приложение отзывчиво.
TTFD — это когда с приложением можно взаимодействовать. Важно сделать это как можно короче, чтобы избежать разочарования пользователя. Если вы правильно сигнализируете TTFD, вы сообщаете системе, что код, который выполняется на пути к TTFD, является частью запуска приложения. В результате система с большей вероятностью поместит этот код в профиль.
Чтобы ваше приложение ощущалось отзывчивым, поддерживайте значения TTID и TTFD на минимально возможном уровне.
Система может обнаружить TTID, отобразить его в Logcat и сообщить об этом как часть бенчмарков запуска. Однако система не может определить TTFD, и приложение обязано сообщить, когда оно достигает полностью нарисованного интерактивного состояния. Вы можете сделать это, вызвав reportFullyDrawn()
или ReportDrawn
если вы используете Jetpack Compose. Если у вас есть несколько фоновых задач, которые все должны быть завершены, прежде чем приложение будет считаться полностью нарисованным, вы можете использовать FullyDrawnReporter
, как описано в разделе Улучшение точности синхронизации запуска .
Библиотечные профили и пользовательские профили
При оценке влияния профилей может быть сложно отделить преимущества профилей вашего приложения от профилей, предоставляемых библиотеками, такими как библиотеки Jetpack. Когда вы создаете свой APK, плагин Android Gradle добавляет любые профили в зависимости библиотеки, а также ваш пользовательский профиль. Это хорошо для оптимизации общей производительности и рекомендуется для ваших релизных сборок. Однако это затрудняет измерение того, насколько дополнительный прирост производительности достигается за счет вашего пользовательского профиля.
Быстрый способ вручную увидеть дополнительную оптимизацию, предоставляемую вашим пользовательским профилем, — удалить его и запустить ваши бенчмарки. Затем замените его и снова запустите ваши бенчмарки. Сравнение двух покажет вам оптимизации, предоставляемые только профилями библиотеки и профилями библиотеки плюс ваш пользовательский профиль.
Автоматизированный способ сравнения профилей — создание нового варианта сборки, который содержит только профили библиотеки, а не ваш пользовательский профиль. Сравните результаты тестов этого варианта с вариантом выпуска, который содержит как профили библиотеки, так и ваши пользовательские профили. В следующем примере показано, как настроить вариант, который включает только профили библиотеки. Добавьте новый вариант с именем releaseWithoutCustomProfile
в ваш модуль потребителя профиля, который обычно является вашим модулем приложения:
Котлин
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")) } } }
Круто
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
.
Котлин
android { ... buildTypes { ... // Release build with only library profiles. create("releaseWithoutCustomProfile") {} ... } ... }
Круто
android { ... buildTypes { ... // Release build with only library profiles. releaseWithoutCustomProfile {} ... } ... }
При запуске теста из Android Studio выберите вариант releaseWithoutCustomProfile
для измерения производительности только с использованием профилей библиотеки или выберите вариант release
для измерения производительности с использованием профилей библиотеки и пользовательских профилей.
Избегайте запуска приложений, связанных с вводом-выводом
Если ваше приложение выполняет много вызовов ввода-вывода или сетевых вызовов во время запуска, это может негативно повлиять как на время запуска приложения, так и на точность вашего бенчмаркинга при запуске. Эти тяжелые вызовы могут занимать неопределенное количество времени, которое может меняться со временем и даже между итерациями одного и того же бенчмарка. Вызовы ввода-вывода, как правило, лучше, чем сетевые вызовы, поскольку на последние могут влиять внешние по отношению к устройству факторы и на самом устройстве. Избегайте сетевых вызовов во время запуска. Если использование одного или другого неизбежно, используйте ввод-вывод.
Мы рекомендуем сделать архитектуру вашего приложения поддерживающей запуск приложения без сетевых или I/O вызовов, даже если только использовать ее при бенчмаркинге запуска. Это помогает обеспечить минимально возможную изменчивость между различными итерациями ваших бенчмарков.
Если ваше приложение использует Hilt, вы можете предоставить фиктивные реализации ввода-вывода при бенчмаркинге в Microbenchmark и Hilt .
Охват всех важных пользовательских путей
Важно точно охватить все важные пользовательские пути в генерации базового профиля. Любые пользовательские пути, которые не охвачены, не будут улучшены базовыми профилями. Наиболее эффективные базовые профили включают все общие пользовательские пути запуска, а также чувствительные к производительности пользовательские пути внутри приложения, такие как прокрутка списков.