Un simple chien de garde qui détecte les ANR Android (Application Not Responding).
Il n'existe actuellement aucun moyen pour une application Android de détecter et de signaler les erreurs ANR.
Si votre application n'est pas dans le Play Store (soit parce que vous la développez encore, soit parce que vous la distribuez différemment), la seule façon d'enquêter sur un ANR est d'extraire le fichier /data/anr/traces.txt.
De plus, nous avons constaté que l’utilisation du Play Store n’était pas aussi efficace que la possibilité de choisir notre propre service de suivi des bugs.
Il y a une entrée de problème dans le tracker de bugs Android décrivant ce manque, n'hésitez pas à la mettre en vedette ;)
Il configure un minuteur « de surveillance » qui détectera le moment où le thread de l'interface utilisateur cesse de répondre. Lorsque c'est le cas, cela génère une erreur avec toutes les traces de pile de threads (principal en premier).
Oui! Je suis heureux que vous ayez demandé : c'est la raison pour laquelle il a été développé en premier lieu !
Comme cela génère une erreur, un gestionnaire de crash peut l'intercepter et la gérer comme il le souhaite.
Les reporters d'accidents en activité connus comprennent :
setReportMainThreadOnly()
)Et il n'y a aucune raison pour que cela ne fonctionne pas avec [insérez ici votre système de rapport d'accidents préféré] .
Le chien de garde est un simple thread qui effectue les opérations suivantes en boucle :
Dans le fichier app/build.gradle
, ajoutez :
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
Dans votre classe d'application, dans onCreate
, ajoutez :
new ANRWatchDog (). start ();
Téléchargez le dernier pot
Mettez le jar dans le répertoire libs/
de votre projet
La trace de pile ANRError
est un peu particulière, elle contient les traces de pile de tous les threads exécutés dans votre application. Ainsi, dans le rapport, chaque section caused by
la section n'est pas la cause de l'exception précédente , mais la trace de pile d'un thread différent.
Voici un exemple de blocage :
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)
À partir de ce rapport, nous pouvons voir que la pile trace deux threads. Le premier (le thread "principal") est bloqué sur MainActivity.java:46
tandis que le deuxième thread (nommé "App: Locker") est verrouillé dans une veille sur MainActivity.java:18
.
À partir de là, si l’on regardait ces deux lignes, on comprendrait sûrement la cause du blocage !
Notez que certaines bibliothèques de rapports de crash (telles que Crashlytics) signalent toutes les traces de pile de threads au moment d'une exception non interceptée. Dans ce cas, avoir tous les threads dans la même exception peut être fastidieux. Dans de tels cas, utilisez simplement setReportMainThreadOnly()
.
Pour définir un délai d'expiration différent (5 000 millis est la valeur par défaut) :
if ( BuildConfig . DEBUG == false ) {
new ANRWatchDog ( 10000 /*timeout*/ ). start ();
}
Par défaut, le chien de garde ignorera les ANR si le débogueur est connecté ou si l'application attend que le débogueur se connecte. En effet, il détecte les pauses d'exécution et les points d'arrêt en tant qu'ANR. Pour désactiver cela et lancer une ANRError
même si le débogueur est connecté, vous pouvez ajouter setIgnoreDebugger(true)
:
new ANRWatchDog (). setIgnoreDebugger ( true ). start ();
Si vous préférez ne pas planter l'application lorsqu'un ANR est détecté, vous pouvez activer un rappel à la place :
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 ();
Ceci est très important lors de la livraison de votre application en production. Lorsqu'il est entre les mains de l'utilisateur final, il est probablement préférable de ne pas planter après 5 secondes, mais simplement de signaler l'ANR au système de reporting que vous utilisez. Peut-être qu'après quelques secondes supplémentaires, l'application se "dégelera".
Si vous souhaitez que seuls vos propres threads soient signalés dans ANRError, et pas tous les threads (y compris les threads système tels que le thread FinalizerDaemon
), vous pouvez définir un préfixe : seuls les threads dont le nom commence par ce préfixe seront signalés. .
new ANRWatchDog (). setReportThreadNamePrefix ( "APP:" ). start ();
Ensuite, lorsque vous démarrez un fil de discussion, n'oubliez pas de définir son nom sur quelque chose qui commence par ce préfixe (si vous souhaitez qu'il soit signalé) :
public class MyAmazingThread extends Thread {
@ Override
public void run () {
setName ( "APP: Amazing!" );
/* ... do amazing things ... */
}
}
Si vous souhaitez avoir uniquement la trace de la pile de threads principale et pas tous les autres threads, vous pouvez :
new ANRWatchDog (). setReportMainThreadOnly (). start ();
Parfois, vous souhaitez savoir que l'application s'est bloquée pendant une certaine durée, mais ne pas signaler l'erreur ANR pour l'instant. Vous pouvez définir un intercepteur qui sera appelé avant de signaler une erreur. Le rôle de l'intercepteur est de définir si, compte tenu de la durée de gel donnée, une erreur ANR doit être déclenchée ou reportée.
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 ;
}
})
Dans cet exemple, l'ANRWatchDog démarre avec un délai d'attente de 2 000 ms, mais l'intercepteur retardera l'erreur jusqu'à ce qu'au moins 5 000 ms de gel aient été atteintes.
ANRWatchDog est un fil de discussion, vous pouvez donc l'interrompre à tout moment.
Si vous programmez avec la capacité multi-processus d'Android (comme démarrer une activité dans un nouveau processus), n'oubliez pas que vous aurez besoin d'un thread ANRWatchDog par processus.
L'utilisation d'ANR-Watchdog est gratuite, à la fois pour un usage commercial et à but non lucratif, et le sera toujours.
Si vous souhaitez montrer votre soutien ou votre appréciation à mon travail, vous êtes libre de faire un don !
Cela serait (bien sûr) grandement apprécié mais n'est en aucun cas nécessaire pour recevoir de l'aide ou du soutien, que je me ferai un plaisir de vous fournir gratuitement :)