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 seu app de perfis fornecidos por bibliotecas, como Bibliotecas Jetpack. Quando você cria seu APK, o Plug-in do Android para Gradle adiciona perfis em dependências de biblioteca, bem como seu perfil personalizado. Isso é bom para otimizar o desempenho geral e é recomendada para builds de lançamento. No entanto, é difícil medir o ganho adicional de desempenho do seu perfil personalizado.
Uma maneira rápida de ver manualmente a otimização adicional oferecida por seus é removê-lo e fazer as comparações. Em seguida, substitua e execute seu os comparativos de mercado novamente. A comparação dos dois mostra as otimizações oferecidas pela os perfis de biblioteca, os perfis de biblioteca e seu perfil personalizado.
Uma maneira automatizada de comparar perfis é criando uma nova variante de build que
contém apenas os perfis da biblioteca e não seu perfil personalizado. Comparar
comparativos de mercado desta variante para a variante de lançamento que contém as
os perfis de biblioteca e seus perfis personalizados. O exemplo a seguir mostra como
para configurar a variante que inclui apenas perfis de biblioteca. Adicionar uma nova variante
chamado releaseWithoutCustomProfile
ao módulo do consumidor de 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 todos os
variantes e a aplica seletivamente apenas à variante release
. Pode parecer que
contraintuitivo de que os perfis da biblioteca ainda estão sendo adicionados quando o
no módulo do produtor de perfil é removida. No entanto, este módulo é
o único responsável por gerar seu perfil personalizado. O Gradle para Android
plug-in ainda está em execução para todas as variantes e é responsável por incluir
os perfis de biblioteca.
Também é necessário 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 um
Variante releaseWithoutCustomProfile
para medir o desempenho apenas com a biblioteca
perfis ou selecione uma variante release
para medir o desempenho com a biblioteca
e perfis 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.
Caso seu app use o Hilt, é possível fornecer um vínculo falso de E/S implementações 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.