Este documento mostra as práticas recomendadas para ajudar a diagnosticar problemas e garantir que seus perfis de referência funcionem corretamente, para oferecer o máximo de benefícios.
Problemas de build
Se você copiou o exemplo de perfis de referência no app de exemplo Now in Android (link em inglês), é possível que encontre falhas no teste durante a tarefa do perfil de referência, afirmando que os testes não podem ser executados em um emulador:
./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):
...
As falhas ocorrem porque o Now in Android usa um dispositivo gerenciado pelo Gradle para gerar perfis de referência. As falhas são esperadas, porque geralmente não é recomendável executar comparações de desempenho em um emulador. No entanto, como você não está coletando métricas de desempenho ao gerar perfis de referência, é possível coletar esses perfis em emuladores por conveniência. Para usar perfis de referência com um emulador, execute o build e a instalação na linha de comando e defina um argumento para ativar as regras de perfis de referência:
installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
Como alternativa, você pode criar uma configuração de execução personalizada no Android Studio para ativar perfis de referência em emuladores, selecionando Run > Edit Configurations:
Problemas de instalação
Confira se o APK ou AAB que você está criando é de uma variante de build que inclui
perfis de referência. A maneira mais fácil de verificar isso é abrindo o APK no
Android Studio selecionando Build > Analyze APK, abrindo seu
APK e procurando o perfil no arquivo
/assets/dexopt/baseline.prof
:
Os perfis de referência precisam ser compilados no dispositivo que executa o app. Para
instalações da app store e apps instalados usando
PackageInstaller
, a compilação no dispositivo acontece como parte do processo
de instalação do app. No entanto, quando o app é transferido por sideload do Android Studio ou
usando ferramentas de linha de comando, a biblioteca ProfileInstaller
do Jetpack é
responsável por enfileirar os perfis para compilação durante o processo seguinte
de otimização de DEX em segundo plano. Nesses casos, se você quiser garantir que os
perfis de referência estejam sendo usados, talvez seja necessário
forçar a compilação de perfis de referência. O ProfileVerifier
permite
consultar o status da instalação e compilação do perfil, conforme mostrado no
exemplo abaixo:
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); } }
Os códigos de resultado abaixo dão dicas sobre a causa de alguns problemas:
RESULT_CODE_COMPILED_WITH_PROFILE
- O perfil foi instalado e compilado, e será usado sempre que o app for executado. Esse é o resultado que você quer encontrar.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
- Nenhum perfil foi encontrado no APK ou AAB em execução. Verifique se você está usando uma variante de build que inclua perfis de referência caso esse erro apareça e se o APK contém um perfil.
RESULT_CODE_NO_PROFILE
- Nenhum perfil foi instalado para este app durante a instalação pela app
store ou pelo gerenciador de pacotes. O principal motivo para o código do erro é que o instalador do
perfil não foi executado, porque o
ProfileInstallerInitializer
estava desativado. Quando esse erro é informado, um perfil incorporado ainda é encontrado no APK do aplicativo. Quando um perfil incorporado não é encontrado, o código do erro retornado éRESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
. RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
- Um perfil foi encontrado no APK ou AAB e foi colocado na fila para compilação. Quando um
perfil é instalado pelo
ProfileInstaller
, ele é colocado na fila para compilação na próxima vez que a otimização DEX em segundo plano for executada pelo sistema. O perfil não fica ativo até que a compilação seja concluída. Não tente comparar seus perfis de referência até que a compilação seja concluída. Talvez seja necessário forçar a compilação de perfis de referência. Esse erro não vai ocorrer quando o app for instalado pela app store ou pelo gerenciador de pacotes em dispositivos com o Android 9 (API 28) ou versões mais recentes, porque a compilação é realizada durante a instalação. RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
- Um perfil não correspondente foi instalado e o app foi compilado com ele.
Isso acontece porque a instalação foi feita pela Google Play Store ou pelo gerenciador de pacotes.
Esse resultado é diferente de
RESULT_CODE_COMPILED_WITH_PROFILE
, porque o perfil não correspondente compila apenas os métodos que ainda estão compartilhados entre o perfil e o app. O perfil é efetivamente menor do que o esperado e menos métodos serão compilados do que os incluídos no perfil de referência. RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier
não pode gravar o arquivo de cache de resultados da verificação. Isso pode acontecer porque há algo errado com as permissões da pasta do app ou porque não há espaço livre em disco suficiente no dispositivo.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
- O ProfileVerifier
is running on an unsupported API version of Android. ProfileVerifier
oferece suporte apenas ao Android 9 (nível 28 da API) e versões mais recentes. RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
- Uma
PackageManager.NameNotFoundException
é gerada ao consultar oPackageManager
do pacote do app. Isso raramente deveria acontecer. Tente desinstalar o app e reinstalar tudo. RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
- Há um arquivo de cache de resultados da verificação anterior, mas ele não pode ser lido. Isso raramente deveria acontecer. Tente desinstalar o app e reinstalar tudo.
Usar o ProfileVerifier na produção
Na produção, você pode usar o ProfileVerifier
com
bibliotecas de relatórios de análise, como o Google Analytics para Firebase, para
gerar eventos de análise que indicam o status do perfil. Por exemplo, isso
vai alertar você rapidamente se for lançada uma nova versão do app que não contenha
perfis de referência.
Forçar a compilação de perfis de referência
Se o status de compilação dos perfis de referência for
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
, você poderá forçar a compilação
imediata usando adb
:
adb shell cmd package compile -r bg-dexopt PACKAGE_NAME
Verificar o estado da compilação sem o ProfileVerifier
Caso não esteja usando o ProfileVerifier
, é possível verificar o estado de compilação usando o
adb
, embora ele não forneça insights tão profundos quanto o ProfileVerifier
:
adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME
O uso do adb
produz algo semelhante a:
[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]
O valor indica o status de compilação do perfil e é um dos valores abaixo:
Status da compilação | Significado |
---|---|
speed‑profile |
Um perfil compilado existe e está sendo usado. |
verify |
Não existe um perfil compilado. |
Um status verify
não significa que o APK ou o AAB não contém um perfil,
porque ele pode ser colocado na fila para compilação pela próxima tarefa de otimização de DEX
em segundo plano.
O motivo do valor indica o que aciona a compilação do perfil e é um destes valores:
Motivo | Significado |
---|---|
install‑dm
|
Um perfil de referência foi compilado manualmente ou pelo Google Play quando o app foi instalado. |
bg‑dexopt
|
Um perfil foi compilado enquanto seu dispositivo estava inativo. Pode ser um perfil de referência ou um perfil coletado durante o uso do app. |
cmdline
|
A compilação foi acionada usando o adb. Pode ser um perfil de referência ou um perfil coletado durante o uso do app. |
Problemas de desempenho
Esta seção mostra algumas práticas recomendadas para definir e comparar corretamente seus perfis de referência e aproveitar ao máximo os benefícios deles.
Comparar corretamente as métricas de inicialização
Os perfis de referência serão mais eficazes se as métricas de inicialização forem bem definidas. As duas métricas principais são tempo para exibição inicial (TTID, na sigla em inglês) e tempo para exibição total (TTFD, na sigla em inglês).
O TTID representa quando o app renderiza o primeiro frame. É importante que ele seja o mais curto possível, porque quando algo aparece na tela, o usuário sabe que o app está em execução. Você pode até mesmo mostrar um indicador de progresso indeterminado para mostrar que o app é responsivo.
O TTFD ocorre quando é possível interagir com o app. É importante que ele seja o mais breve possível para evitar a frustração do usuário. Se você sinalizar corretamente o TTFD, vai informar ao sistema que o código executado no caminho para o TTFD faz parte da inicialização do app. É mais provável que o sistema coloque esse código no perfil como um resultado.
Mantenha o TTID e o TTFD o mais curtos possíveis para tornar o app responsivo.
O sistema consegue detectar o TTID, mostrá-lo no Logcat e informar como parte
das comparações de inicialização. No entanto, o sistema não consegue determinar o TTFD, e é
responsabilidade do app informar quando atinge um estado interativo totalmente
renderizado. Para fazer isso, chame reportFullyDrawn()
ou
ReportDrawn
, se você estiver usando o Jetpack Compose. Se você tiver várias
tarefas em segundo plano que precisam ser concluídas antes que o app seja considerado totalmente
renderizado, use FullyDrawnReporter
, conforme descrito em
Melhorar a precisão da marcação do tempo de inicialização.
Perfis de biblioteca e perfis personalizados
Ao comparar o impacto dos perfis, pode ser difícil separar os benefícios dos perfis do app daqueles fornecidos por bibliotecas, como as bibliotecas do Jetpack. Quando você cria o APK, o Plug-in do Android para Gradle adiciona todos os perfis nas dependências de biblioteca e ao seu perfil personalizado. Isso é bom para otimizar o desempenho geral e é recomendado para builds de lançamento. No entanto, fica difícil medir o ganho de desempenho extra proveniente do perfil personalizado.
Uma maneira rápida de conferir manualmente a otimização extra fornecida pelo seu perfil personalizado é removê-la e executar as comparações. Em seguida, substitua a API e execute as comparações novamente. A comparação dos dois vai mostrar as otimizações oferecidas apenas pelos perfis da biblioteca, além do seu personalizado.
Uma maneira automatizável de comparar perfis é criando uma nova variante de build que
contenha apenas os perfis da biblioteca, e não o personalizado. Compare
as comparações dessa variante com a de lançamento que contém os
perfis de biblioteca e personalizados. O exemplo abaixo mostra como
configurar a variante que inclui apenas perfis da biblioteca. Adicione uma nova variante
chamada releaseWithoutCustomProfile
ao módulo de consumidor do perfil, que normalmente é
o módulo do app:
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")) } } }
O exemplo de código anterior remove a dependência baselineProfile
de todas
as variantes e a aplica seletivamente apenas à variante release
. Pode parecer
não intuitivo que os perfis da biblioteca ainda sejam adicionados quando a
dependência no módulo do produtor de perfil é removida. No entanto, esse módulo é
responsável apenas por gerar seu perfil personalizado. O Plug-in
do Android para Gradle ainda está em execução para todas as variantes e é responsável por incluir
perfis de biblioteca.
Você também precisa adicionar a nova variante ao módulo do gerador de perfis. Neste
exemplo, o módulo do produtor é chamado de :baselineprofile
.
Kotlin
android { ... buildTypes { ... // Release build with only library profiles. create("releaseWithoutCustomProfile") {} ... } ... }
Groovy
android { ... buildTypes { ... // Release build with only library profiles. releaseWithoutCustomProfile {} ... } ... }
Ao executar a comparação no Android Studio, selecione uma
variante releaseWithoutCustomProfile
para medir o desempenho apenas com perfis
de biblioteca ou uma variante release
para medir o desempenho com perfis de biblioteca
e personalizados.
Evitar a inicialização do app vinculada à E/S
Se o app estiver realizando muitas chamadas de E/S ou de redes durante a inicialização, isso poderá afetar negativamente o tempo de inicialização do app e a precisão da comparação de inicialização. Essas chamadas pesadas podem levar um tempo indeterminado, que pode variar até mesmo entre iterações da mesma comparação. As chamadas de E/S geralmente são melhores que as chamadas de rede, porque as últimas podem ser afetadas por fatores externos ao dispositivo e no próprio dispositivo. Evite chamadas de rede durante a inicialização. Quando o uso de um ou outro tipo de chamada for inevitável, use as chamadas de E/S.
Recomendamos que a arquitetura do app ofereça suporte à inicialização do app sem chamadas de rede ou de E/S, mesmo que seja apenas para uso durante a comparação da inicialização. Isso ajuda a garantir a menor variabilidade possível entre diferentes iterações das comparações.
Se o app usa o Hilt, você pode fornecer implementações falsas vinculadas de E/S ao fazer comparações na Microbenchmark e no Hilt.
Abordar todas as jornadas importantes do usuário
É importante abordar com cuidado todas as jornadas importantes do usuário na geração de perfis de referência. As jornadas do usuário que não forem cobertas não serão melhoradas pelos perfis de referência. Os perfis de referência mais eficazes incluem todas as jornadas comuns do usuário na inicialização, além das jornadas do usuário no app que exigem maior desempenho, como listas de rolagem.