Visão geral da avaliação do desempenho de apps

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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 desenhados na tela na cadência solicitada (60 Hz ou mais). Instabilidade é 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.
  • Latência de inicializaçã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 quente" 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.

      • Para 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 suaves

    • Essas preocupações surgem durante interações, como alternar entre guias 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

    • Trabalho gasta bateria, e trabalhos desnecessários 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 um trabalho significativo no sistema. Isso ocorre porque não só as alocações demandam esforço do Android Runtime, 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 ao risco de código não sustentável não é a escolha certa, considerando o que o ART é capaz de fazer.

      Porém, ainda há trabalho envolvido, por isso, lembre-se de verificar se você está alocando muitos objetos no loop interno, o que pode contribuir para problemas de desempenho.

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
  • 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 usar FrameMetricsAggregator para registrar métricas de instabilidade durante um fluxo de trabalho específico.
    • Testes de laboratório
      • Jetpack Macrobenchmark: rolagem
      • O MacroBenchmark coleta um tempo 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étricas RenderTime, que destacam o tempo que os frames estão demorando para desenhar, são mais importantes que a contagem de frames instáveis para identificar regressões ou melhorias.

Configurar o app para análise de desempenho

A configuração adequada é essencial para conseguir comparativos precisos e acionáveis 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 a seguir 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 uma 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 (API de nível 29) e versões mais recentes podem usar o profileable android:shell="true" no manifesto para ativar a criação de perfil em versões de lançamento.

Use a configuração de redução de código de nível de 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 de desempenho significativa, e você a atingirá com frequência se estiver reinstalando 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" 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 por usá-los, confirme se eles estão coletando o que você espera. Os perfis estão localizados aqui:

/data/misc/profiles/ref/[package-name]/primary.prof

O 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 fazem 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 ver no rastro de exemplo a seguir, um activityStart é imediatamente seguido por outro activityStart sem que nenhum frame seja desenhado pela primeira atividade.

alt_text

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:

alt_text

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):

alt_text

Se você diminuir o zoom e navegar pelo rastro, poderá ver 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:

alt_text

Quando há uma interrupção nessa cadência regular, isso é um frame instável:

alt_text

Com um pouco mais de prática, você poderá vê-las facilmente.

alt_text

Em alguns casos, você precisará aumentar o zoom nesse ponto de rastreamento para ver 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 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.
  • Deixar de oferecer suporte a RecyclerViews aninhados adequadamente, fazendo com que o RecyclerView interno seja completamente recriado todas as vezes.
    • Todo RecyclerView interno e aninhado precisa ter um RecycledViewPool definido para garantir que as visualizações possam ser recicladas entre RecyclerViews internos.
  • Sem pré-busca de dados suficientes ou sem 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 "instabilidade", já que nenhum prazo de frame está sendo perdido, pode ser uma grande melhoria na experiência do usuário para modificar o tempo e a quantidade de pré-busca para que o usuário não tenha para esperar os dados.