Nesta seção, mostramos como depurar um erro O app não está respondendo (ANR) usando
ProfilingManager com um exemplo de trace.
Configurar o app para coletar ANRs
Comece configurando um acionador de ANR no app:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
Depois de capturar e fazer upload de um rastro de ANR, abra-o na interface do Perfetto.
Analisar o trace
Como o ANR acionou o rastro, você sabe que ele terminou quando o sistema detectou falta de resposta na linha de execução principal do app. A Figura 1 mostra como navegar até a linha de execução principal do app, que é marcada de acordo na interface.
O fim do rastro corresponde ao carimbo de data/hora do ANR, como mostrado na Figura 2.
O trace também mostra as operações que o app estava executando quando o ANR ocorreu.
Especificamente, o app executou o código na fração de trace handleNetworkResponse. Essa
fração estava dentro da fração MyApp:SubmitButton. Ele usou 1,48 segundo de tempo de CPU (Figura 3).
Se você depender apenas de stack traces no momento do ANR para depuração, poderá
atribuir o ANR inteiramente ao código em execução na
fração de trace handleNetworkResponse, que não havia terminado quando o perfil
concluiu a gravação. No entanto,
1,48 segundo não é suficiente para acionar um ANR por conta própria, mesmo que seja uma
operação cara. É preciso analisar mais para trás no tempo para entender o que
bloqueou a linha de execução principal antes desse método.
Para encontrar um ponto de partida para procurar a causa do ANR, começamos a procurar
depois do último frame gerado pela linha de execução de interface, que corresponde à
fração Choreographer#doFrame 551275, e não há grandes fontes de atraso antes
de iniciar a fração MyApp:SubmitButton que terminou no ANR (Figura 4).
Para entender o bloqueio, diminua o zoom para inspecionar toda a fração
MyApp:SubmitButton. Você vai notar um detalhe crítico nos estados da linha de execução, conforme mostrado na
Figura 4: a linha de execução passou 75% do tempo (6,7 segundos) no estado Sleeping
e apenas 24% do tempo no estado Running.
Isso indica que a causa principal do ANR foi a espera, não a computação. Analise as ocorrências de sono individuais para encontrar um padrão.
Os três primeiros intervalos de suspensão (Figuras 6 a 8) são quase idênticos, com aproximadamente 2 segundos cada. Um quarto sono atípico (Figura 9) é de 0,7 segundo. Uma duração de exatamente dois segundos raramente é uma coincidência em um ambiente de computação. Isso sugere fortemente um tempo limite programado, em vez de uma contenção de recursos aleatória. A última suspensão pode ser causada pela linha de execução que encerra a espera porque a operação que ela estava aguardando foi concluída.
A hipótese é que o app estava atingindo um tempo limite definido pelo usuário de 2 segundos várias vezes e, por fim, conseguindo, causando atraso suficiente para acionar um ANR.
Para verificar isso, inspecione o código associado à seção de trace
MyApp:SubmitButton:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } void prepareNetworkRequest() { // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
O código confirma essa hipótese. O método onClickSubmit executa uma solicitação de rede
na linha de execução de interface com um NETWORK_TIMEOUT_MILLISECS fixado no código de 2.000 ms.
O mais importante é que ele é executado em um loop while que tenta novamente até 10 vezes.
Nesse trace específico, é provável que o usuário tenha tido uma conectividade de rede ruim. As três primeiras tentativas falharam, causando três tempos limite de 2 segundos (um total de 6 segundos).
A quarta tentativa foi bem-sucedida após 0,7 segundo, permitindo que o código prossiga para
handleNetworkResponse. No entanto, o tempo de espera acumulado já acionou
o ANR.
Evite esse tipo de ANR colocando operações relacionadas à rede que têm latências variadas em uma linha de execução em segundo plano, em vez de executá-las na linha de execução principal. Isso permite que a IU permaneça responsiva mesmo com conectividade ruim, eliminando completamente essa classe de ANRs.