I. Introduction▲
C'est inévitable, lorsque l'on développe on fait des bogues (bugs). Personne n'y échappe, même les meilleurs. Il est donc primordial d'être en mesure de trouver et comprendre les bogues pour pouvoir les résoudre.
Android Studio propose tous les outils nécessaires pour déboguer votre code. Cet article se limitera au code Java, mais sachez qu'il est également possible de déboguer du code C ou C++ ainsi que du Kotlin.
Nous aborderons uniquement l'utilisation du Logcat qui est la première étape du débogage. Vous pourrez ensuite aller encore plus loin à l'aide de points d'arrêt et du mode pas à pas.
II. Prérequis▲
Pour déboguer il faut un appareil qui le permet. Sur votre téléphone/tablette il faudra donc vous rendre dans les options développeur et activer le débogage USB.
Si vous utilisez un émulateur, il n'y a rien à faire, tout est prévu par défaut.
À la création d'un projet Android Studio il faut créer également automatiquement une variante débogage de votre application. Elle n'est pas visible dans le fichier gradle, mais elle est bien présente.
III. Lire et écrire des journaux▲
L'interface principale pour trouver des informations de débogage est le Logcat. C'est une vue d'Android Studio qui vous permet de voir tous les journaux écrits par le système ou les applications.
Il est accessible via les onglets en bas de l'écran ou via le raccourci « Alt+6 »
III-A. Lire▲
Beaucoup d'informations s'affichent dans le Logcat , et la plupart d'entre elles ne nous concernent pas. Il faut donc filtrer les messages selon plusieurs critères :
- L'appareil concerné ;
- Le processus concerné ;
- La sévérité du message ;
- Un filtre textuel ;
- Le périmètre des journaux.
Le but est de ne voir que les messages concernant l'application que l'on souhaite déboguer et rien d'autre.
Une fois les filtres appliqués, il faut encore comprendre ce que l'on voit. Fort heureusement tous les messages dans le Logcat ont la même forme :
[date] [heure] [PID-TID/package] [sévérité/tag] [message]
Par exemple :
11-13 13:02:50.071 1901-4229/com.google.android.gms V/AuthZen: Handling delegate intent.
Que nous apprend donc ce message ?
Le 13 novembre à 13h02 l'application com.google.android.gms affiche le message « Handling delegate intent. » de sévérité verbose avec le tag (l'étiquette) AuthZen.
Vous remarquerez aussi différentes couleurs de message. Chaque couleur représente une sévérité de message parmi verbose, debug, info, warning et error. Une chose simple à retenir : quand c'est rouge, ce n'est jamais bon signe !
III-B. Écrire▲
Maintenant que l'on sait lire le Logcat, nous allons profiter de ses avantages pour afficher nos propres messages.
Cette technique est la méthode de débogage la plus simple qui consiste à ajouter des « traces » dans son code pour vérifier une valeur par exemple.
L'écriture dans le Logcat se fait à l'aide de la classe Log (https://developer.android.com/reference/android/util/Log.html)
Elle permet d'écrire un message dans la sévérité voulue. Par exemple pour écrire une info :
Log.i
(
tag,message) ;
pour un message de débogage :
Log.d
(
tag,message) ;
Etc.
Le paramètre tag est une chaîne de caractère libre ; par convention, on la définit généralement au nom de la classe en cours, ce qui permet de savoir rapidement d'où émane un message.
Le paramètre message quant à lui est le message que l'on souhaite afficher.
Donc par exemple le code suivant :
int
age =
8
;
Log.d
(
"MonActivity"
,"Mon age est de "
+
age);
Va afficher :
11-16 13:00:38.856 4580-4580/com.demo.debug D/MonActivity: Mon age est de 8.
IV. Analyser une stack trace▲
IV-A. Stack trace ?▲
Maintenant que vous maîtrisez le Logcat , vous devriez être capable d'y trouver ce qu'on appelle une stack trace (pile d'appel en bon français). Une stack trace est un message d'erreur détaillé vous indiquant un problème particulier et de quelle façon il s'est produit. Avec une stack trace vous êtes en théorie capable de savoir quelle ligne de votre code pose problème et comment ce code a été appelé.
IV-B. Analyse▲
Pour le besoin de cet article, je vais créer volontairement une erreur : j'essaie d'utiliser un widget qui n'existe pas dans le layout chargé par mon activité :
Button btn =
findViewById
(
R.id.existepas);
btn.setText
(
"Test"
);
Le résultat est un plantage au niveau de l'application :
Et inévitablement, on trouve une stack trace dans le Logcat :
11-20 14:27:56.159 4837-4837/com.demo.debug E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.demo.debug, PID: 4837
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo.debug/com.demo.debug.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setText(java.lang.CharSequence)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setText(java.lang.CharSequence)' on a null object reference
at com.demo.debug.MainActivity.onCreate(MainActivity.java:16)
at android.app.Activity.performCreate(Activity.java:5990)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Il y a énormément d'information dans cette trace :
Nous voyons que nous sommes face à une RuntimeException , et plus précisément que le système n'arrive pas à charger l'activité MainActivity :
Unable to start activity ComponentInfo{com.demo.debug/com.demo.debug.MainActivity}
On voit juste après que nous sommes face à une java.lang.NullPointerException.
Rien pour le moment qui nous permettrait d'identifier la source du problème…
Sauf que si on descend un peu dans la trace, on va trouver la ligne suivante :
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setText(java.lang.CharSequence)' on a null object reference
La ligne Caused by nous indique quelle est la source l'erreur. Ici une NullPointerException lors de l'appel à setText(), et si on descend encore un peu dans la trace on nous indique même l'endroit exact :
at com.demo.debug.MainActivity.onCreate(MainActivity.java:16)
Vous remarquerez que le texte entre parenthèses est cliquable, ce qui va vous emmener directement au bon endroit dans le code. De plus, si le code concerné est le vôtre, le texte s'affichera en bleu contrairement aux autres fichiers qui seront en gris.
C'est donc la fonction onCreate() de MainActivity qui cause problème, et plus précisément la ligne 16.
Ne reste alors qu'à se rendre dans le code pour voir la ligne en question :
btn.setText
(
"Test"
);
On peut donc en déduire que la variable « btn » est null, ce qui cause l'erreur.
Pour supprimer l'erreur, il faut donc corriger ce problème.
Dans notre cas, on a deux solutions, soit faire un test avant d'utiliser la variable btn :
if
(
btn!=
null
)
btn.setText
(
"Test"
);
Soit de s'assurer que la vue recherchée par findViewById
(
) existe bien.
IV-C. Les grands classiques▲
NullPointerException
Certainement l'erreur la plus emblématique de Java, qui signifie que l'on essaie d'utiliser une variable null.
Solution : vérifier la nullité d'une variable avant de l'utiliser.
ActivityNotFoundException
Cette erreur intervient quand on essaie d'appeler une activité via startActivity(), mais que cette activity n'est pas déclarée dans le manifest.
Solution : déclarer l'activity dans le manifest.
ClassCastException
Cette erreur survient quand on essaie de lancer un objet dans une instance d'objet inappropriée. Par exemple lancer un TextView en View est tout à fait valide. En revanche lancer un TextView en Integer va provoquer une erreur.
Solution : toujours lancer dans le bon type.
NetworkOnMainThreadException
Dans Android, il est formellement interdit de créer une requête réseau sur le threadUI. Si vous le faites, vous recevrez cette erreur.
Solution : faire les requêtes réseau dans une asynctask ou dans un thread à part.
Activity Has Leaked Window That Was Originally Added Here
Cette erreur survient très souvent quand on essaie d'afficher un dialogue après avoir quitté l'activité à laquelle il est lié.
Solution : toujours détruire les dialog dans onPause() ou onDestroy().