Um watchdog simples que detecta Android ANRs (Application Not Responding).
Atualmente, não há como um aplicativo Android detectar e relatar erros de ANR.
Se o seu aplicativo não estiver na Play Store (seja porque você ainda o está desenvolvendo ou porque o está distribuindo de forma diferente), a única maneira de investigar um ANR é extrair o arquivo /data/anr/traces.txt.
Além disso, descobrimos que usar a Play Store não era tão eficaz quanto poder escolher nosso próprio serviço de rastreamento de bugs.
Há uma entrada de problema no rastreador de bugs do Android descrevendo essa falta, sinta-se à vontade para marcá-la com uma estrela;)
Ele configura um cronômetro "watchdog" que detectará quando o thread da UI para de responder. Quando isso acontece, gera um erro com todos os rastreamentos de pilha de threads (principal primeiro).
Sim! Que bom que você perguntou: essa é a razão pela qual ele foi desenvolvido!
Como isso gera um erro, um manipulador de falhas pode interceptá-lo e tratá-lo da maneira necessária.
Repórteres de acidentes conhecidos incluem:
setReportMainThreadOnly()
)E não há razão para que ele não funcione com [insira seu sistema de relatório de falhas favorito aqui] .
O watchdog é um thread simples que faz o seguinte em um loop:
No arquivo app/build.gradle
, adicione:
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
Na sua classe de aplicação, em onCreate
, adicione:
new ANRWatchDog (). start ();
Baixe o jar mais recente
Coloque o jar no diretório libs/
do seu projeto
O rastreamento de pilha ANRError
é um pouco particular, pois contém os rastreamentos de pilha de todos os threads em execução em seu aplicativo. Portanto, no relatório, cada seção caused by
não é a causa da exceção precedente , mas o rastreamento de pilha de um thread diferente.
Aqui está um exemplo de dead lock:
FATAL EXCEPTION: |ANR-WatchDog|
Process: anrwatchdog.github.com.testapp, PID: 26737
com.github.anrwatchdog.ANRError: Application Not Responding
Caused by: com.github.anrwatchdog.ANRError$_$_Thread: main (state = WAITING)
at testapp.MainActivity$1.run(MainActivity.java:46)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
Caused by: com.github.anrwatchdog.ANRError$_$_Thread: APP: Locker (state = TIMED_WAITING)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1031)
at java.lang.Thread.sleep(Thread.java:985)
at testapp.MainActivity.SleepAMinute(MainActivity.java:18)
at testapp.MainActivity.access$100(MainActivity.java:12)
at testapp.MainActivity$LockerThread.run(MainActivity.java:36)
A partir deste relatório, podemos ver que os rastreamentos de pilha de dois threads. O primeiro (o thread "principal") está preso em MainActivity.java:46
enquanto o segundo thread (denominado "App: Locker") está bloqueado em suspensão em MainActivity.java:18
.
A partir daí, se olharmos para essas duas linhas, certamente entenderemos a causa do dead lock!
Observe que algumas bibliotecas de relatórios de falhas (como Crashlytics) relatam todos os rastreamentos de pilha de threads no momento de uma exceção não detectada. Nesse caso, ter todos os threads na mesma exceção pode ser complicado. Nesses casos, basta usar setReportMainThreadOnly()
.
Para definir um tempo limite diferente (5.000 milissegundos é o padrão):
if ( BuildConfig . DEBUG == false ) {
new ANRWatchDog ( 10000 /*timeout*/ ). start ();
}
Por padrão, o watchdog ignorará ANRs se o depurador estiver anexado ou se o aplicativo estiver aguardando a conexão do depurador. Isso ocorre porque ele detecta pausas de execução e pontos de interrupção como ANRs. Para desabilitar isso e lançar um ANRError
mesmo se o depurador estiver conectado, você pode adicionar setIgnoreDebugger(true)
:
new ANRWatchDog (). setIgnoreDebugger ( true ). start ();
Se preferir não travar o aplicativo quando um ANR for detectado, você pode ativar um retorno de chamada:
new ANRWatchDog (). setANRListener ( new ANRWatchDog . ANRListener () {
@ Override
public void onAppNotResponding ( ANRError error ) {
// Handle the error. For example, log it to HockeyApp:
ExceptionHandler . saveException ( error , new CrashManager ());
}
}). start ();
Isso é muito importante ao entregar seu aplicativo em produção. Quando estiver nas mãos do usuário final, provavelmente é melhor não travar após 5 segundos, mas simplesmente reportar o ANR para qualquer sistema de relatório que você usar. Talvez, depois de mais alguns segundos, o aplicativo “descongele”.
Se você quiser que apenas seus próprios threads sejam relatados no ANRError, e não todos os threads (incluindo threads do sistema como o thread FinalizerDaemon
), você pode definir um prefixo: apenas os threads cujo nome começa com este prefixo serão relatados .
new ANRWatchDog (). setReportThreadNamePrefix ( "APP:" ). start ();
Então, quando você iniciar um tópico, não esqueça de definir seu nome para algo que comece com este prefixo (se você quiser que ele seja relatado):
public class MyAmazingThread extends Thread {
@ Override
public void run () {
setName ( "APP: Amazing!" );
/* ... do amazing things ... */
}
}
Se quiser ter apenas o rastreamento de pilha do thread principal e não todos os outros threads, você pode:
new ANRWatchDog (). setReportMainThreadOnly (). start ();
Às vezes, você deseja saber se o aplicativo congelou por um determinado período, mas ainda não deseja relatar o erro ANR. Você pode definir um interceptor que será chamado antes de reportar um erro. A função do interceptor é definir se, dada a duração do congelamento, um erro ANR deve ou não ser gerado ou adiado.
new ANRWatchDog ( 2000 ). setANRInterceptor ( new ANRWatchDog . ANRInterceptor () {
@ Override
public long intercept ( long duration ) {
long ret = 5000 - duration ;
if ( ret > 0 ) {
Log . w ( TAG , "Intercepted ANR that is too short (" + duration + " ms), postponing for " + ret + " ms." );
}
return ret ;
}
})
Neste exemplo, o ANRWatchDog inicia com um timeout de 2.000 ms, mas o interceptor adiará o erro até que pelo menos 5.000 ms de congelamento sejam atingidos.
ANRWatchDog é um thread, então você pode interrompê-lo a qualquer momento.
Se você estiver programando com a capacidade de múltiplos processos do Android (como iniciar uma atividade em um novo processo), lembre-se de que precisará de um thread ANRWatchDog por processo.
O ANR-Watchdog é gratuito para uso comercial e sem fins lucrativos e sempre será.
Se você deseja mostrar algum apoio ou agradecimento ao meu trabalho, você está livre para doar !
Isso seria (é claro) muito apreciado, mas não é de forma alguma necessário para receber ajuda ou suporte, que terei prazer em fornecer gratuitamente :)