Esta seção mostra como depurar um erro "O app não está respondendo" (ANR) usando
ProfilingManager com um exemplo de rastreamento.
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 o upload de um rastreamento de ANR, abra-o na interface do Perfetto.
Analisar o rastreamento
Como o ANR acionou o rastreamento, 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 final do rastreamento corresponde ao carimbo de data/hora do ANR, conforme mostrado na Figura 2.
O rastreamento também mostra as operações que o app estava executando quando o ANR ocorreu.
Especificamente, o app executou o código na divisão de rastreamento handleNetworkResponse. Essa divisão estava dentro da divisão MyApp:SubmitButton. Ela usou 1,48 segundo de tempo de CPU (Figura 3).
Se você confiar apenas em stack traces no momento do ANR para depuração, poderá atribuir o ANR incorretamente ao código em execução na divisão de rastreamento handleNetworkResponse, que não havia terminado quando o perfil terminou a gravação. No entanto, 1,48 segundo é insuficiente para acionar um ANR por conta própria, mesmo que seja uma operação cara. É necessário voltar mais no tempo para entender o que bloqueou a linha de execução principal antes desse método.
Para ter um ponto de partida para procurar a causa do ANR, começamos a procurar após o último frame gerado pela linha de execução da interface, que corresponde à Slice Choreographer#doFrame 551275, e não há grandes fontes de atraso antes de iniciar a Slice MyApp:SubmitButton que terminou no ANR (Figura 4).
Para entender o bloqueio, diminua o zoom para inspecionar a divisão MyApp:SubmitButton completa. Você vai notar um detalhe importante 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. Examine as ocorrências de suspensão individuais para encontrar um padrão.
Os três primeiros intervalos de suspensão (Figuras 6 a 8) são quase idênticos, aproximadamente 2 segundos cada. Um quarto tempo de suspensão atípico (Figura 9) é de 0,7 segundo. Uma duração de exatamente 2 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 termina a espera porque a operação que ela estava aguardando foi bem-sucedida.
A hipótese é que o app estava atingindo um tempo limite definido pelo usuário de 2 segundos várias vezes e, por fim, sendo bem-sucedido, causando atraso suficiente para acionar um ANR.
Para verificar isso, inspecione o código associado à seção de rastreamento 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 da interface do usuário com um NETWORK_TIMEOUT_MILLISECS fixado no código de 2.000 ms.
É importante ressaltar que ele é executado dentro de um loop while que tenta novamente até 10 vezes.
Nesse rastreamento específico, o usuário provavelmente tinha 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 prosseguisse 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 variáveis em uma linha de execução em segundo plano, em vez de executá-las na linha de execução principal. Isso permite que a interface permaneça responsiva mesmo com conectividade ruim, eliminando completamente essa classe de ANRs.