Este tópico ajuda a identificar e corrigir os principais problemas de desempenho no seu app.
Principais problemas de desempenho
Há muitos problemas que podem contribuir para o desempenho ruim em um app, mas veja algumas situações comuns a serem observadas:
- Instabilidade de rolagem
- "Instabilidade" é o termo usado para descrever o problema visual que ocorre quando o sistema não pode criar e fornecer frames a tempo para eles serem mostrados na tela na frequência solicitada (60 Hz ou mais). Ela é mais aparente ao rolar, quando é preciso ter um fluxo animado fluído, e o movimento pausa ao longo de um ou mais frames, já que o app leva mais tempo para renderizar o conteúdo do que a duração de um frame no sistema.
- O valor desejável de taxas de atualização do app é de 90 Hz. As taxas de renderização tradicionais eram
de 60 Hz, mas muitos dispositivos mais recentes operam no modo 90 Hz durante interações
com o usuário, como rolagem, e alguns dispositivos aceitam taxas ainda mais altas
(até 120 Hz).
- Para ver a taxa de atualização usada por um dispositivo em um determinado momento, ative uma sobreposição em Opções do desenvolvedor > Mostrar taxa de atualização na seção Depuração.
-
- A latência de inicialização é o tempo que leva entre o toque no ícone do aplicativo, na notificação ou em outro ponto de entrada e a exibição dos dados do usuário na tela.
Você precisa seguir estas duas metas de inicialização nos seus apps:
Inicialização a frio < 500 ms: uma "inicialização a frio" ocorre quando o app que está sendo iniciado não está presente na memória do sistema. Isso acontece quando é a primeira inicialização do app desde a reinicialização ou quando o processo do app é eliminado pelo usuário ou pelo sistema.
Por outro lado, uma inicialização com estado salvo ocorre quando o app já está sendo executado em segundo plano. Uma inicialização a frio exige o máximo de trabalho do sistema, porque ela precisa carregar tudo do armazenamento e inicializar o app. Tenha como meta a inicialização a frio com duração de até 500 ms.
Fazer com que as latências de P95/P99 fiquem muito próximas à latência mediana. Às vezes, quando o app leva muito tempo para iniciar, a confiança do usuário é danificada. As IPCs e a E/S desnecessária durante o caminho crítico de inicialização do app podem enfrentar a contenção de bloqueios e introduzir essas inconsistências.
Transições que não são estáveis
- Essas preocupações surgem durante interações, como mudat de guia ou carregar uma nova atividade. Esses tipos de transição precisam ter animações suaves e não incluir atrasos ou oscilação visual.
Ineficiências de energia
- Atividades gastam bateria, e ações desnecessárias reduzem a vida útil da bateria.
As alocações de memória, provenientes da criação de novos objetos no código, podem ser a causa de uma atividade significativa no sistema. Isso ocorre porque não só as alocações demandam esforço do Android Runtime (ART), mas liberar esses objetos posteriormente ("coleta de lixo") também requer tempo e esforço. A alocação e a coleta são muito mais rápidas e mais eficientes do que costumavam ser, especialmente para objetos temporários. Então, quando a orientação costumava ser evitar a alocação de objetos sempre que possível, a recomendação agora é fazer o que for mais adequado para seu app e sua arquitetura. Salvar alocações colocando o código em risco não é a escolha certa, considerando o que o ART é capaz de fazer.
Porém, ainda há trabalho envolvido, por isso, não esqueça de verificar se você está alocando muitos objetos no loop interno, o que pode contribuir para problemas de performance.
Identificar problemas
O fluxo de trabalho recomendado para identificar e corrigir problemas de desempenho é o seguinte:
- Identifique as jornadas ideais do usuário a serem inspecionadas. Esses fatos podem incluir:
- Fluxos de inicialização comuns, incluindo aqueles da tela de início e notificação.
- Todas as telas em que o usuário rola os dados.
- Transições entre telas.
- Fluxos de longa duração, como navegação ou reprodução de música.
- Inspecione o que acontece durante esses fluxos usando ferramentas de depuração:
- Systrace ou Perfetto: permite ver exatamente o que está acontecendo em todo o dispositivo com dados de velocidade precisos.
- Memory Profiler: permite ver quais alocações de memória estão acontecendo no heap.
- Simpleperf: veja um diagrama das chamadas de função que consomem mais CPU durante um determinado período. Quando você identifica algo que está demorando muito no Systrace, mas não sabe o motivo, o Simpleperf pode fornecer mais informações.
A depuração manual de execuções de teste individuais é fundamental para entender e depurar esses problemas de desempenho. As etapas acima não podem ser substituídas pela análise dos dados agregados. No entanto, configurar a coleta de métricas em testes automatizados e em campo também é importante para entender o que os usuários estão realmente vendo e identificar quando podem ocorrer regressões:
- Fluxos de inicialização
- Métricas de campo: tempo de inicialização do Play Console.
- Testes de laboratório Jetpack Macrobenchmark: inicialização
- Instabilidade
- Métricas de campo
- Frames vitais no Play Console: observe que, dentro do Play Console, não é possível reduzir as métricas para uma jornada de usuário específica, uma vez que tudo que é reportado é instabilidade geral pelo app.
- Medição personalizada com
FrameMetricsAggregator
: é possível usarFrameMetricsAggregator
para registrar métricas de instabilidade durante um fluxo de trabalho específico.
- Testes de laboratório
- Jetpack Macrobenchmark: rolagem
- O MacroBenchmark coleta o tempo para a renderização do frame usando os comandos
dumpsys gfxinfo
que formam uma única jornada do usuário. Essa é uma maneira razoável de entender a variação na instabilidade em uma jornada específica do usuário. As métricasRenderTime
, que destacam o tempo que os frames estão demorando para carregar, são mais importantes que a contagem de frames instáveis para identificar regressões ou melhorias.
- Métricas de campo
Configurar o app para análise de desempenho
A configuração adequada é essencial para conseguir comparativos precisos e que gerem ações práticas a partir de um aplicativo. Faça o teste em um sistema o mais próximo possível da produção e suprimindo fontes de ruído. As seções abaixo são uma série de etapas específicas do APK e do sistema que você pode seguir para preparar uma configuração de teste, e algumas delas são específicas ao caso de uso.
Pontos de rastreamento
Os aplicativos podem instrumentar o código com eventos de rastreamento personalizados.
Enquanto os rastros estão sendo capturados, o rastreamento incorre em uma pequena sobrecarga (aproximadamente 5μs) por seção. Portanto, não o coloque em cada método. O rastreamento de blocos de trabalho maiores (> 0,1 ms) pode fornecer insights significativos sobre os gargalos.
Considerações sobre o APK
Cuidado: não avalie o desempenho em um build de depuração.
As variantes de depuração podem ser úteis para solucionar problemas e simbolizar amostras de pilha,
mas têm impactos não lineares graves no desempenho. Dispositivos que executam
o Android 10 (nível 29 da API) e versões mais recentes podem usar o
profileable android:shell="true"
no manifesto para ativar a criação de perfil em builds de lançamento.
Use a configuração que reduz código no nível da produção. Dependendo dos recursos usados pelo aplicativo, isso pode ter um impacto significativo no desempenho. Algumas configurações do ProGuard removem pontos de acesso. Por isso, considere remover essas regras para a configuração em que os testes serão executados.
Compilação
Compile seu aplicativo no dispositivo para um estado conhecido (geralmente "speed" ou "speed-profile"). A atividade JIT em segundo plano pode ter uma sobrecarga significativa de performance, e você a atinge com frequência se reinstala o APK entre as execuções de teste. O comando para fazer isso é:
adb shell cmd package compile -m speed -f com.google.packagename
O modo de compilação "speed" compila completamente o app. O modo "speed-profile" vai compilar o aplicativo de acordo com um perfil dos caminhos de código utilizados coletados durante o uso do aplicativo. Pode ser difícil coletar perfis de forma consistente e correta. Portanto, se você optar pelo uso deles, confirme se estão coletando o que você espera. Os perfis estão localizados aqui:
/data/misc/profiles/ref/[package-name]/primary.prof
A Macrobenchmark permite que você especifique diretamente o modo de compilação.
Considerações do sistema
Para medidas de baixo nível e alta fidelidade, calibre seus dispositivos. Execute comparações A/B no mesmo dispositivo e na mesma versão do SO. Podem existir variações significativas no desempenho, mesmo em dispositivos do mesmo tipo.
Em dispositivos com acesso root, use um
script lockClocks
para microcomparativos. Entre outras coisas, esses scripts podem fazer o seguinte:
- Colocar CPUs com uma frequência fixa,
- Desativar os núcleos pequenos para configurar a GPU.
- Desativar a limitação térmica.
Isso não é recomendado para testes focados na experiência do usuário, como lançamento de apps, teste DoU e testes de instabilidade, mas pode ser essencial para reduzir o ruído em testes de microcomparação.
Sempre que possível, use um framework de teste como o Macrobenchmark, que pode reduzir o ruído nas medições e evitar imprecisão delas.
Inicialização lenta do app: atividade trampolim desnecessária
Uma atividade trampolim pode estender o tempo de inicialização do app desnecessariamente, e é
importante saber se o app está fazendo isso. Como é possível notar no rastro de exemplo
abaixo, um activityStart
é imediatamente seguido por outro
activityStart
, sem que nenhum frame seja mostrado pela primeira atividade.
Isso pode acontecer em um ponto de entrada de notificação e em um de entrada normal de inicialização de apps e, muitas vezes, pode ser resolvido com a refatoração. Por exemplo, se você estiver usando essa atividade para realizar a configuração antes da execução de outra atividade, fatore o código em um componente ou biblioteca reutilizável.
Alocações desnecessárias acionam GCs frequentes
Observe que as coletas de lixo (GCs, na sigla em inglês) estão acontecendo com mais frequência do que o esperado em um systrace.
Nesse caso, cada 10 segundos, durante uma operação de longa duração, é um indicador de que seu app pode estar alocando de forma desnecessária, mas consistente ao longo do tempo:
Você também pode notar que uma pilha de chamadas específica faz a grande maioria das alocações ao usar o Memory Profiler. Você não precisa eliminar todas as alocações de forma agressiva, já que isso pode dificultar a manutenção do código. Comece trabalhando em pontos de acesso de alocações.
Frames com instabilidade
O pipeline de gráficos é relativamente complicado, e pode haver algumas nuances envolvidas na determinação de um usuário que pode ter visto um frame descartado. Em alguns casos, a plataforma pode "resgatar" um frame usando armazenamento em buffer. No entanto, você pode ignorar a maior parte dessas nuances para identificar facilmente frames problemáticos da perspectiva do app.
Quando os frames estão sendo desenhados com pouco trabalho do app, os
pontos de rastreamento Choreographer.doFrame()
ocorrem em uma cadência de 16,7 ms (presumindo um dispositivo de 60
FPS):
Se você diminuir o zoom e navegar pelo rastro, vai notar que os frames demoram um pouco para serem concluídos, mas tudo bem, porque eles não estão levando mais tempo do que 16,7 ms:
Quando há uma interrupção nessa cadência regular, isso é um frame instável:
Com um pouco mais de prática, você vai conseguir identificar a instabilidade facilmente.
Em alguns casos, você precisa aumentar o zoom nesse ponto de rastreamento para conferir mais informações sobre quais visualizações estão infladas ou o que o RecyclerView está fazendo. Em outros, pode ser preciso inspecionar mais.
Para saber mais sobre como identificar frames instáveis e depurar as causas, consulte Renderização lenta.
Erros comuns do RecyclerView
- Invalidar todos os dados de apoio do
RecyclerView
desnecessariamente. Isso pode levar a tempos de renderização de frames longos e, portanto, instabilidade. Em vez disso, invalide apenas os dados que foram alterados, para minimizar o número de visualizações que precisam ser atualizadas.- Consulte Como apresentar dados
dinâmicos
para saber como evitar chamadas caras do
notifyDatasetChanged()
, o que faz com que o conteúdo seja atualizado, em vez de ser totalmente substituído.
- Consulte Como apresentar dados
dinâmicos
para saber como evitar chamadas caras do
- Deixar de oferecer suporte a
RecyclerView
s aninhados adequadamente, fazendo com que oRecyclerView
interno seja completamente recriado todas as vezes.- Todo
RecyclerView
interno e aninhado precisa ter umRecycledViewPool
definido para garantir que as visualizações possam ser recicladas entreRecyclerView
s internos.
- Todo
- Falta de pré-busca de dados suficientes ou pré-busca em tempo hábil. Pode ser desagradável atingir rapidamente o fim de uma lista de rolagem e ter que esperar mais dados do servidor. Embora não seja tecnicamente uma "instabilidade", já que nenhum prazo de frame está sendo perdido, pode ser uma grande melhoria na experiência do usuário modificar o tempo e a quantidade de pré-busca para o usuário não ter que esperar os dados.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Otimização e análise de inicialização do app {:#app-startup-analysis-optimization}
- Frames congelados
- Como criar uma comparação da Macrobenchmark