Android - como investigar um ANR?

153

Existe uma maneira de descobrir onde meu aplicativo lançou um ANR (aplicativo não está respondendo). Dei uma olhada no arquivo traces.txt em / data e vejo um rastreamento para o meu aplicativo. É isso que vejo no traço.

DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433af808
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=9 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x433ac2a0
  | sysTid=694 nice=0 sched=0/0 handle=1367136
  at dalvik.system.NativeStart.run(Native Method)

"Signal Catcher" daemon prio=5 tid=7 RUNNABLE
  | group="system" sCount=0 dsCount=0 s=0 obj=0x433ac1e8
  | sysTid=693 nice=0 sched=0/0 handle=1366712
  at dalvik.system.NativeStart.run(Native Method)

"HeapWorker" daemon prio=5 tid=5 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x4253ef88
  | sysTid=692 nice=0 sched=0/0 handle=1366472
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

Como posso descobrir onde está o problema? Os métodos no rastreamento são todos os métodos do SDK.

Obrigado.

lostInTransit
fonte
2
Eu tenho um relatório desse tipo, também acontecendo em android.os.MessageQueue.nativePollOnce(Native Method). Posso ignorá-lo com segurança?
rds

Respostas:

124

Um ANR acontece quando alguma operação longa ocorre no encadeamento "principal". Esse é o encadeamento do loop de eventos e, se estiver ocupado, o Android não poderá processar mais eventos da GUI no aplicativo e, portanto, exibirá uma caixa de diálogo ANR.

Agora, no rastreamento que você postou, o thread principal parece estar indo bem, não há problema. Ele está ocioso no MessageQueue, aguardando a chegada de outra mensagem. No seu caso, o ANR provavelmente foi uma operação mais longa, em vez de algo que bloqueou o encadeamento permanentemente; portanto, o encadeamento de eventos se recuperou após o término da operação e seu rastreamento passou. após o ANR.

Detectar onde os ANRs acontecem é fácil se for um bloco permanente (conflito obtendo alguns bloqueios por exemplo), mas mais difícil se for apenas um atraso temporário. Primeiro, repasse seu código e procure por pontos remuneráveis ​​e operações de longa duração. Os exemplos podem incluir o uso de soquetes, bloqueios, suspensão de linha e outras operações de bloqueio de dentro da linha de eventos. Você deve garantir que tudo isso ocorra em threads separados. Se nada parecer o problema, use DDMS e ative a exibição do encadeamento. Isso mostra todos os threads em seu aplicativo semelhantes ao rastreamento que você possui. Reproduza o ANR e atualize o thread principal ao mesmo tempo. Isso deve mostrar exatamente o que está acontecendo no momento da ANR

em breve
fonte
6
o único problema é "reproduzir o ANR" :-). você poderia explicar como o thread principal do show de rastreamento de pilha está 'inativo', isso seria ótimo.
Blundell 30/03
20
O rastreamento de pilha mostra que o encadeamento principal está no Looper (a implementação do loop de mensagens) e está fazendo uma espera temporizada no Object.wait. Isso significa que os loops de mensagens atualmente não têm nenhuma mensagem a ser despachada e aguardam a chegada de novas mensagens. Um ANR acontece quando o sistema percebe que um loop de mensagem está gastando muito tempo processando uma mensagem e não processando outras mensagens no fila. Se os loops estiverem aguardando mensagens, obviamente isso não está acontecendo.
sooniln
3
@Soonil Oi, você sabe o que significa o restante das seções, como Binder thread 3, Binder thread 2 JDWP demon prio 5. o que é sCount, dsCount, obj, sysTid, bom agendamento significa. também tem informações como VMWAIT, RUNNABLE, NATIVE
minhaz
1
Meu aplicativo é baseado em NDK, vejo o mesmo ANR. Além disso, o thread principal está bom. Tentei o DDMS e atualizei o thread de trabalho quando ele congela. Infelizmente, tudo o que recebo é uma única linha NativeStart :: run. A exibição do encadeamento DDMS é capaz de inspecionar encadeamentos NDK nativos? Além disso: StrictMode não encontrou nada.
Bram
6
Consulte elliotth.blogspot.com/2012/08/… para obter uma boa explicação da saída.
sooniln
96

Você pode ativar o StrictMode no nível 9 da API e acima.

O StrictMode é mais comumente usado para capturar acesso acidental ao disco ou à rede no encadeamento principal do aplicativo, onde as operações da interface do usuário são recebidas e as animações ocorrem. Ao manter o encadeamento principal do seu aplicativo responsivo, você também impede que as caixas de diálogo ANR sejam exibidas aos usuários.

public void onCreate() {
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                           .detectAll()
                           .penaltyLog()
                           .penaltyDeath()
                           .build());
    super.onCreate();
}

Ao usar, penaltyLog()você pode assistir à saída do adb logcat enquanto usa seu aplicativo para ver as violações à medida que elas ocorrem.

Dheeraj Vepakomma
fonte
StrictMode não pode ser resolvido para um tipo. Preciso importar primeiro algo? Pressionar CTRL + SHIFT + O não ajuda.
Kuchi
23
pequena ponta - uso se (BuildConfig.DEBUG) ... para evitar a inclusão na produção
Amir Uval
@uval, o que você quer dizer com "impedir a inclusão na produção"? !!
Muhammed Refaat 01/04/2015
2
@MuhammedRefaat não impede nenhum ANR. Ele travará o aplicativo imediatamente em vez de após 5 segundos. Por exemplo, se você acessar o banco de dados no thread principal e demorar 2 segundos, não receberá um ANR, mas o StrictMode travará o aplicativo. StrictMode é estritamente para sua fase de depuração, não produção.
Amir Uval
1
@MuhammedRefaat adicionou minha resposta à sua pergunta.
Amir Uval #
80

Você está se perguntando qual tarefa contém um thread da interface do usuário. O arquivo de rastreamento fornece uma dica para encontrar a tarefa. você precisa investigar um estado de cada thread

Estado do encadeamento

  • running - executando código do aplicativo
  • sleeping - chamado Thread.sleep ()
  • monitor - aguardando a aquisição de um bloqueio de monitor
  • espera - em Object.wait ()
  • native - executando código nativo
  • vmwait - aguardando um recurso da VM
  • zumbi - o fio está morrendo
  • init - o thread está sendo inicializado (você não deve ver isso)
  • partida - o tópico está prestes a começar (você também não deve ver isso)

Concentre-se no estado SUSPENDED, MONITOR. O estado do monitor indica qual thread é investigado e o estado SUSPENDED do thread é provavelmente o principal motivo do conflito.

Etapas básicas de investigação

  1. Encontre "esperando para bloquear"
    • você pode encontrar o estado do monitor "Thread Binder # 15" prio = 5 tid = 75 MONITOR
    • você tem sorte se encontrar "esperando para bloquear"
    • exemplo: aguardando para bloquear <0xblahblah> (um com.foo.A) mantido por threadid = 74
  2. Você pode perceber que "tid = 74" mantém uma tarefa agora. Então vá para tid = 74
  3. tid = 74 estado talvez SUSPENSO! encontre o principal motivo!

o rastreio nem sempre contém "aguardando bloqueio". neste caso, é difícil encontrar o motivo principal.

Horyun Lee
fonte
1
Boa explicação. Agora ficou mais fácil entender os logs ANR. Mas ainda tenho um problema para entender porque, na etapa 1, sou capaz de encontrar facilmente o ID do encadeamento, mas quando, na etapa 2, estou tentando ir onde está, para verificar o estado, não consigo encontrá-lo. . Alguma idéia de como proceder?
THZ
1
Eu tenho - waiting to lock an unknown objectdentro "HeapTaskDaemon" daemon prio=5 tid=8 Blocked . O que significa alguém pode ajudar?
Hilal
13

Eu tenho aprendido android nos últimos meses, por isso estou longe de ser um especialista, mas fiquei muito decepcionado com a documentação sobre ANRs.

A maioria dos conselhos parece ter como objetivo evitá-los ou corrigi-los, examinando cegamente seu código, o que é ótimo, mas não consegui encontrar nada sobre a análise do rastreamento.

Há três coisas que você realmente precisa procurar nos logs ANR.

1) Deadlocks: quando um thread está no estado WAIT, você pode ver os detalhes para descobrir quem é "holdby =". Na maioria das vezes, ele será mantido por si só, mas se for mantido por outro segmento, é provável que seja um sinal de perigo. Vá olhar para esse tópico e veja o que ele contém. Você pode encontrar um loop, que é um sinal claro de que algo deu errado. Isso é muito raro, mas é o primeiro ponto, porque quando isso acontece, é um pesadelo

2) Linha principal em espera: se sua linha principal estiver no estado WAIT, verifique se ela está retida por outra linha. Isso não deve acontecer, porque o thread da interface do usuário não deve ser mantido por um thread em segundo plano.

Ambos os cenários significam que você precisa refazer seu código significativamente.

3) Operações pesadas no thread principal: esta é a causa mais comum de ANRs, mas às vezes é uma das mais difíceis de encontrar e corrigir. Veja os detalhes principais do thread. Role para baixo o rastreamento da pilha e até ver as classes que você reconhece (do seu aplicativo). Observe os métodos no rastreamento e descubra se você está fazendo chamadas de rede, chamadas de banco de dados, etc. nesses locais.

Finalmente, e peço desculpas por conectar descaradamente meu próprio código, você pode usar o analisador de logs python que escrevi em https://github.com/HarshEvilGeek/Android-Log-Analyzer Isso examinará seus arquivos de log, abrirá arquivos ANR, encontre deadlocks, encontre threads principais em espera, encontre exceções não capturadas nos logs do agente e imprima tudo na tela de uma maneira relativamente fácil de ler. Leia o arquivo Leia-me (que vou adicionar) para saber como usá-lo. Isso me ajudou muito na semana passada!

Akhil Cherian Verghese
fonte
4

Sempre que você está analisando problemas de tempo, a depuração geralmente não ajuda, pois congelar o aplicativo em um ponto de interrupção fará com que o problema desapareça.

Sua melhor aposta é inserir muitas chamadas de log (Log.XXX ()) nos diferentes threads e retornos de chamada do aplicativo e ver onde está o atraso. Se você precisar de um rastreamento de pilha, crie uma nova exceção (apenas instancia uma) e registre-a.

Ulrich
fonte
2
Obrigado pelo conselho sobre a criação de uma nova exceção, se você precisar de um rastreamento de pilha. Isso é muito útil quando a depuração :)
Kuchi
3

O que dispara o ANR?

Geralmente, o sistema exibe um ANR se um aplicativo não puder responder à entrada do usuário.

Em qualquer situação em que seu aplicativo execute uma operação potencialmente longa, você não deve executar o trabalho no thread da interface do usuário, mas criar um thread de trabalho e executar a maior parte do trabalho lá. Isso mantém o thread da interface do usuário (que aciona o loop de eventos da interface do usuário) em execução e evita que o sistema conclua que seu código congelou.

Como evitar ANRs

Aplicativos Android normalmente são executados inteiramente em um único thread, por padrão, o "thread da interface do usuário" ou "thread principal"). Isso significa que qualquer coisa que seu aplicativo esteja fazendo no encadeamento da interface do usuário que leva muito tempo para concluir pode acionar a caixa de diálogo ANR porque o aplicativo não está dando a si mesmo a chance de manipular o evento de entrada ou as transmissões de intenção.

Portanto, qualquer método executado no thread da interface do usuário deve fazer o mínimo possível de trabalho nesse thread. Em particular, as atividades devem fazer o mínimo possível para configurar os principais métodos de ciclo de vida, como onCreate () e onResume (). Operações potencialmente demoradas, como operações de rede ou banco de dados, ou cálculos computacionalmente caros, como redimensionar bitmaps, devem ser feitos em um encadeamento de trabalho (ou no caso de operações de bancos de dados, por meio de uma solicitação assíncrona).

Código: thread de trabalho com a classe AsyncTask

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

Código: Executar segmento do Trabalhador

Para executar este segmento de trabalho, basta criar uma instância e chamar execute ():

new DownloadFilesTask().execute(url1, url2, url3);

Fonte

http://developer.android.com/training/articles/perf-anr.html

Jack
fonte
1

meu problema com o ANR, depois de muito trabalho, descobri que um thread estava chamando um recurso que não existia no layout, em vez de retornar uma exceção, recebi o ANR ...

yaniv
fonte
que é extremamente estranho
Nilabja
0

Básico na resposta @Horyun Lee, escrevi um pequeno script python para ajudar a investigar o ANR detraces.txt .

Os ANRs serão exibidos como gráficos graphvizse você tiver instalado grapvhvizno seu sistema.

$ ./anr.py --format png ./traces.txt

Um png será exibido como abaixo se houver ANRs detectados no arquivo traces.txt. É mais intuitivo.

insira a descrição da imagem aqui

O traces.txtarquivo de exemplo usado acima foi obtido daqui .

alijandro
fonte
0

Considere usar a biblioteca ANR-Watchdog para rastrear e capturar com precisão os rastreamentos de pilha ANR em um alto nível de detalhe. Você pode enviá-los para sua biblioteca de relatórios de falhas. Eu recomendo usarsetReportMainThreadOnly() neste cenário. Você pode fazer com que o aplicativo lance uma exceção não fatal do ponto de congelamento ou faça com que o aplicativo seja encerrado quando o ANR acontecer.

Observe que os relatórios ANR padrão enviados ao console do desenvolvedor do Google Play geralmente não são precisos o suficiente para identificar o problema exato. É por isso que é necessária uma biblioteca de terceiros.

Mr-IDE
fonte