Este documento fornece práticas recomendadas e etapas de solução de problemas 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:
Verificar a instalação e a aplicação do perfil
Para verificar se o APK ou Android App Bundle (AAB) que você está inspecionando é de uma variante de build que inclui perfis de referência, faça o seguinte:
- No Android Studio, selecione Build > Analyze APK.
- Abra o AAB ou APK.
Confirme se o arquivo
baseline.profexiste:- Se você estiver inspecionando um AAB, o perfil estará em
/BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof. Se você estiver inspecionando um APK, o perfil estará em
/assets/dexopt/baseline.prof.A presença desse arquivo é o primeiro sinal de uma configuração de build correta. Se ele estiver faltando, significa que o Android Runtime não vai receber instruções de pré-compilação no momento da instalação.
Figura 2. Verifique se há um perfil de referência usando o APK Analyzer no Android Studio.
- Se você estiver inspecionando um AAB, o perfil estará em
Os perfis de referência precisam ser compilados no dispositivo que executa o app. Quando você
instala builds não depuráveis usando o Android Studio ou a ferramenta de linha de comando
do wrapper do Gradle, a compilação no dispositivo acontece automaticamente. Se você instalar
o app na Google Play Store, os perfis de linha de base serão compilados durante
atualizações em segundo plano do dispositivo, e não no momento da instalação. Quando o app é instalado
usando outras ferramentas, a biblioteca ProfileInstaller do Jetpack é responsável
por enfileirar os perfis para compilação durante o próximo processo 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 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
ProfileInstallerInitializerestava 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 Play 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_FILEProfileVerifiernã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. ProfileVerifieroferece 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 oPackageManagerdo 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 ProfileVerifier em 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 do perfil de referência sem 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. |
Verificar a aplicação do perfil de inicialização no DEX e no r8.json
As regras de perfil de inicialização são usadas no momento da build pelo R8 para otimizar o layout das
classes nos arquivos DEX. Essa otimização no momento da build é diferente de como os
perfis de referência (baseline.prof) são usados, já que eles são empacotados no
APK ou AAB para que o ART execute a compilação no dispositivo. Como as regras de perfil de inicialização
são aplicadas durante o processo de build, não há um arquivo
startup.prof separado no APK ou AAB para inspeção. O efeito dos perfis de inicialização é visível no layout do arquivo DEX.
Inspecionar a organização DEX com r8.json (recomendado para AGP 8.8 ou mais recente)
Para projetos que usam o plug-in do Android para Gradle (AGP) 8.8 ou mais recente, é possível verificar
se o perfil de inicialização foi aplicado inspecionando o arquivo r8.json
gerado. Esse arquivo é empacotado no seu AAB.
- Abra o arquivo AAB e localize o arquivo
r8.json. - Pesquise no arquivo a matriz
dexFiles, que lista os arquivos DEX gerados. Procure um objeto
dexFilesque contenha o par de chave-valor"startup": true. Isso indica explicitamente que as regras do perfil de inicialização foram aplicadas para otimizar o layout desse arquivo DEX específico."dexFiles": [ { "checksum": "...", "startup": true // This flag confirms profile application to this DEX file }, // ... other DEX files ]
Inspecionar a organização DEX para todas as versões do AGP
Se você estiver usando uma versão do AGP anterior à 8.8, inspecionar os arquivos DEX será a principal maneira de verificar se o perfil de inicialização foi aplicado corretamente. Você também pode usar esse método se estiver usando o AGP 8.8 ou mais recente e quiser verificar manualmente o layout DEX. Por exemplo, se você não estiver percebendo as melhorias de performance esperadas. Para inspecionar a organização DEX, faça o seguinte:
- Abra o AAB ou APK usando Build > Analyze APK no Android Studio.
- Navegue até o primeiro arquivo DEX. Por exemplo,
classes.dex. - Inspecione o conteúdo desse arquivo DEX. Você poderá verificar se as
classes e métodos críticos definidos no arquivo de perfil de inicialização
(
startup-prof.txt) estão presentes no arquivo DEX principal. Uma inscrição bem-sucedida significa que esses componentes essenciais para startups são priorizados para carregamento mais rápido.
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 daqueles contribuídos por bibliotecas, como as do Jetpack. Ao criar o APK, o plug-in do Android para Gradle adiciona todos os perfis nas dependências de biblioteca, além do seu perfil personalizado. Isso é bom para otimizar o desempenho geral e é recomendado para builds de lançamento. No entanto, isso dificulta a medição de quanto ganho de performance adicional vem do seu perfil personalizado.
Uma maneira rápida de verificar manualmente a otimização adicional fornecida pelo seu perfil personalizado é remover e executar os comparativos de mercado. Em seguida, substitua e execute os comparativos de mercado novamente. Ao comparar os dois, você vai ver as otimizações fornecidas apenas pelos perfis de biblioteca e pelos perfis de biblioteca mais seu perfil personalizado.
Uma maneira automatizada de comparar perfis é criar uma nova variante de build que contenha apenas os perfis de biblioteca e não o seu perfil personalizado. Compare os comparativos de mercado dessa variante com a variante de lançamento que contém os perfis da biblioteca e os personalizados. O exemplo a seguir mostra como
configurar a variante que inclui apenas perfis de biblioteca. Adicione uma nova variante
chamada releaseWithoutCustomProfile ao módulo consumidor de perfil, que geralmente é
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
contraditório que os perfis de biblioteca ainda sejam adicionados quando a
dependência do módulo 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 gerador de perfil. Neste exemplo, o módulo 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 o comparativo de performance 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, é possível fornecer implementações falsas vinculadas a E/S ao fazer comparativos de mercado em Microbenchmark e 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.
Mudanças no perfil de tempo de compilação do teste A/B
Como os perfis de inicialização e de referência são uma otimização de tempo de compilação, geralmente não há suporte para testes A/B diretos de diferentes APKs usando a Google Play Store em versões de produção. Para avaliar o impacto em um ambiente semelhante ao de produção, considere as seguintes abordagens:
Lançamento fora do ciclo: faça upload de um lançamento fora do ciclo para uma pequena porcentagem da sua base de usuários que inclui apenas a mudança de perfil. Isso permite coletar métricas do mundo real sobre a diferença de performance.
Comparativo de mercado local: compare seu app localmente com e sem o perfil aplicado. No entanto, o comparativo local mostra o melhor cenário possível para os perfis, já que não inclui os efeitos dos perfis da nuvem do ART presentes em dispositivos de produção.