A inicialização representa a primeira impressão que o app tem nos usuários. E os usuários não gostam de esperar, então você precisa garantir que seu app seja iniciado rapidamente. Para mostrar como uma equipe real de desenvolvimento de apps encontrou e diagnosticou problemas na inicialização de um app, veja o que a equipe do Gmail para Wear OS fez.
A equipe do Gmail para Wear OS realizou uma iniciativa de otimização, com foco principal na inicialização do app e no desempenho da renderização no tempo de execução, para atender aos critérios de desempenho da equipe. No entanto, mesmo que você não tenha limites específicos para direcionar, quase sempre haverá espaço para melhorar a inicialização do app se você dedicar algum tempo para investigar.
Capturar um rastro e analisar a inicialização do app
Para começar a análise, capture um rastro que inclua a inicialização do app para uma inspeção mais detalhada no Perfetto ou no Android Studio. Este estudo de caso usa o Perfetto porque mostra o que está acontecendo no sistema do dispositivo, além do seu app. Quando você faz upload do rastro no Perfetto, ele tem a seguinte aparência:
Como o foco é melhorar a inicialização do app, localize a linha com a métrica personalizada Inicialização do app Android. É útil fixá-la na parte superior da visualização clicando no ícone de fixação , que aparece quando você passa o cursor sobre a linha. A barra, ou fatia, que você vê na linha Android App Startups indica o intervalo de tempo que a inicialização do app abrange, até que o primeiro frame seja exibido na tela. Portanto, procure problemas ou gargalos.
Observe que a métrica Android App Startups representa o
tempo para exibição inicial,
mesmo que você esteja usando reportFullyDrawn()
. Para identificar o
tempo para exibição total, procure
reportFullyDrawn()
na caixa de pesquisa do Perfetto.
Verificar a linha de execução principal
Primeiro, examine o que está acontecendo na linha de execução principal. A linha de execução principal é muito importante porque geralmente é onde toda a renderização da interface acontece. Quando ela é bloqueada, nenhum desenho pode acontecer e seu app parece estar congelado. Portanto, é preciso garantir que nenhuma operação de longa duração esteja acontecendo na linha de execução principal.
Para encontrar a linha de execução principal, encontre e expanda a linha com o nome do pacote do app. As duas linhas com o mesmo nome do pacote (geralmente as duas primeiras linhas da seção) representam a linha de execução principal. Das duas linhas de linha de execução principais, a primeira representa o estado da CPU e a segunda representa os pontos de rastreamento. Fixe as duas linhas da linha de execução principais abaixo da métrica Android App Startups.
Tempo gasto no estado executável e na contenção de CPU
Para ter uma visualização agregada da atividade da CPU durante a inicialização do app, arraste o cursor sobre a linha de execução principal para capturar o intervalo de tempo de inicialização do app. O painel Thread States que aparece mostra o tempo total gasto em cada estado da CPU dentro do intervalo de tempo selecionado.
Veja o tempo gasto no estado Runnable
. Quando uma linha de execução está no estado Runnable
,
ela fica disponível para o trabalho, mas nenhum trabalho é programado. Isso pode
indicar que o dispositivo está sobrecarregado e não consegue programar tarefas
de alta prioridade. O app principal visível para o usuário tem a maior prioridade na
programação. Portanto, uma linha de execução principal inativa geralmente indica que processos intensivos
no app, como a renderização de animações, estão competindo com a linha de execução principal
por tempo de CPU.
Quanto maior a proporção de tempo no estado Runnable
para o tempo no estado Running
, maior
a probabilidade de ocorrer contenção de CPU. Ao inspecionar problemas de desempenho
dessa maneira, concentre-se primeiro no frame de execução mais longa e trabalhe para
os menores.
Ao analisar o tempo gasto no estado Runnable
, considere o hardware do dispositivo.
Como o app retratado está sendo executado em um dispositivo wearable com duas CPUs, a
espera-se de mais tempo gasto no estado Runnable
e mais contenção de CPU
com outros processos do que se estivéssemos analisando um dispositivo com mais
CPUs. Embora seja gasto mais tempo no estado Runnable
do que o esperado para um
app típico de smartphones, isso pode ser compreensível no contexto de wearables.
Tempo gasto em OpenDexFilesFromOat*
Agora, verifique o tempo gasto no OpenDexFilesFromOat*
. No trace, isso acontece ao
mesmo tempo que a fração bindApplication
. Essa fração representa o tempo
necessário para ler os arquivos DEX do aplicativo.
Transações de vinculação bloqueadas
Em seguida, verifique as transações de vinculação. As transações de vinculação representam chamadas entre
o cliente e o servidor. Nesse caso, o app (cliente) chama o sistema Android
(servidor) com um binder transaction
e o servidor responde com binder
reply
. Verifique se o app não faz transações de vinculação desnecessárias
durante a inicialização, porque elas aumentam o risco de contenção de CPU. Se possível,
adie o trabalho que envolve chamadas de vinculação após o período de inicialização do app. Se você
precisar fazer transações de binder, elas não podem demorar mais do que a
taxa de atualização de Vsync do dispositivo.
A primeira transação de vinculação, que geralmente ocorre ao mesmo tempo que a
fração ActivityThreadMain
, parece ser bastante longa nesse caso. Para saber
mais sobre o que pode estar acontecendo, siga estas etapas:
- Para ver a resposta de binder associada e saber mais sobre como a transação de binder está sendo priorizada, clique na fração da transação de binder de interesse.
Para conferir a resposta da vinculação, acesse o painel Seleção atual e clique em Resposta da vinculação na seção Conversas seguidas. O campo Linha de execução também informa a linha de execução em que a resposta de vinculação ocorre se você quiser navegar manualmente até ela. O processo será diferente. Será exibida uma linha que conecta a transação de vinculação e a resposta.
Para conferir como o servidor do sistema está processando essa transação de binder, fixe as linhas de execução Cpu 0 e Cpu 1 na parte de cima da tela.
Encontre os processos do sistema que processam a resposta de vinculação encontrando as frações que incluem o nome da linha de execução de resposta de vinculação. Neste caso, "Binder:687_11 [2542]". Clique nos processos relevantes do sistema para conferir mais informações sobre a transação do binder.
Confira este processo do sistema associado à transação de vinculação de interesse que ocorre na CPU 0:
O estado final informa Runnable (Preempted)
, o que significa que o processo está
atrasando porque a CPU está fazendo outra coisa. Para descobrir por que ele está sendo interrompido, expanda as linhas Eventos Ftrace. Na guia Ftrace
Events disponível, role e procure eventos relacionados
à linha de execução de vinculação de interesse "Binder:687_11 [2542]". Perto do momento em que o processo do sistema é interrompido, ocorrem dois eventos do servidor do sistema que incluem o argumento "decon", o que significa que estão relacionados ao controlador de exibição. Isso parece razoável porque o controlador de exibição coloca
os frames na tela, uma tarefa importante. Deixe os eventos como estão.
Atividade JIT
Para investigar a atividade de compilação just-in-time
(JIT),
expanda os processos pertencentes ao app, encontre as duas linhas "Pool de linhas de execução Jit"
e fixe-as na parte superior da visualização. Como esse app se beneficia dos perfis de referência durante a inicialização,
pouca atividade JIT ocorre até que o primeiro frame seja renderizado, o que é indicado pelo
fim da primeira chamada Choreographer.doFrame
. No entanto, observe o motivo de inicialização
lenta JIT compiling void
, que sugere que a atividade do sistema que acontece
durante o tracepoint rotulado como Application creation
está causando muita
atividade JIT em segundo plano. Para resolver isso, adicione os eventos que acontecem logo
após o primeiro frame ser renderizado no perfil de referência expandindo a coleção
de perfis até um ponto em que o app esteja pronto para ser usado. Em muitos casos, é possível
fazer isso adicionando uma linha ao final do teste da biblioteca Macrobenchmark
para coleta de perfis de referência. Ele aguarda até que um widget de interface específico apareça na
tela, indicando que ela está totalmente preenchida.
Resultados
Como resultado dessa análise, a equipe do Gmail para Wear OS fez as seguintes melhorias:
- Como eles notaram contenção durante a inicialização do app ao analisar a atividade da CPU, substituíram a animação do ícone de carregamento usada para indicar que o app está sendo carregado com uma única imagem estática. Eles também prolongaram a tela de apresentação para adiar o estado de brilho, o segundo estado de tela usado para indicar que o app está sendo carregado, para liberar recursos da CPU. Isso melhorou a latência de inicialização do app em 50%.
- Ao analisar o tempo gasto em
OpenDexFilesFromOat*
e atividade JIT, foi possível ativar a reescrita do R8 dos perfis de referência. Isso melhorou a latência de inicialização do app em 20%.
Veja algumas dicas da equipe sobre como analisar o desempenho do app de forma eficiente:
- Configure um processo contínuo que seja capaz de coletar rastros e resultados automaticamente. Configure o rastreamento automatizado para seu app usando a comparação.
- Use o teste A/B para fazer as mudanças que você acha que podem melhorar os resultados e rejeite-as caso não melhorarem. É possível medir o desempenho em diferentes cenários usando a biblioteca Macrobenchmark.
Para saber mais, consulte os seguintes recursos:
- Desempenho: como usar a criação de perfil de amostragem com o Systrace – MAD Skills (link em inglês)
- Desempenho: como capturar rastros do criador de perfil – Habilidades do MAD