Vous codez paisiblement en pyjama dans votre canapé quand soudain, votre application plante ! Eh oui, cela arrive, même aux meilleurs. Lors d'un plantage, un énorme pavé de texte bien rouge s'affiche dans le Logcat d'Android Studio. Dans ce chapitre, nous allons apprendre à analyser ce type d'erreur puis apporter une correction.
Appréhendez la notion de pile d'exécution
Suivant les langages de programmation que vous connaissez, la notion de pile d'exécution (ou call stack, en anglais) vous est probablement familière. Dans le cas contraire, il est nécessaire de comprendre ce principe.
Une pile d'exécution permet de mémoriser l'enchaînement d'appel des méthodes (ou fonctions) d'un programme. Pour ce faire, une structure de données de type pile (ou LIFO en anglais, pour Last In First Out) est utilisée. Ce mécanisme permet donc de garder une trace des fonctions appelées, afin de "revenir sur ses pas" lorsqu'une fonction est terminée.
Pour illustrer cela, nous allons prendre un exemple assez schématique. Imaginez que vous ayez une fonction (ou méthode, cela importe peu) nommée getTemperature() qui vous renvoie la température extérieure. Cette fonction utilise une autre fonction, getCelsiusTemperature(), qui permet d'interroger le thermomètre. Enfin, la fonction getCelsiusTemperature() utilise une dernière fonction nommée convertDegreesToCelsius() qui convertit en degrés Celsius une température initialement en degrés Farenheit. Le code simplifié correspondant serait :
int getTemperature() { return getCelsiusTemperature(); } int getTemperatureFromSensor() { int farenheitTemperature = sensor.getFarenheitTemperature(); return convertDegreesToCelsius(farenheitTemperature); } int convertDegreesToCelsius(int farenheit) { return (farenheit - 32) * 5 / 9; }
Lorsque vous appelez la fonction getTemperature(), elle est automatiquement ajoutée à la pile. La pile contient donc :
Pile |
getTemperature |
Ensuite, au sein de la fonction getTemperature(), c'est au tour de la fonction getCelsiusTemperature() d'être appelée. Elle est également ajoutée à la pile. La pile contient donc :
Pile |
getCelsiusTemperature |
getTemperature |
Enfin, c'est au tour de la fonction convertDegreesToCelsius() d'être appelée. Après avoir été ajoutée à la pile, cette dernière contient donc :
Pile |
convertDegreesToCelsius |
getCelsiusTemperature |
getTemperature |
À la fin de l'exécution d'une fonction, elle est "dépilée", c'est-à-dire qu'elle est retirée de la pile. Il est donc possible de retrouver la fonction appelante.
Analysez un plantage
Il est temps de passer aux choses sérieuses. En l'état, l'application MemeTastic fonctionne correctement. Taquins que nous sommes, nous allons lui forcer un peu la main pour qu'elle se plante lamentablement. Ouvrez le fichier MainActivity.java, repérez la ligne 139, puis commentez-la :
// _tagValues = getResources().getStringArray(R.array.meme_tags__titles);
Cette ligne permet de récupérer la liste des titres à utiliser pour chaque onglet de l'application.
Démarrez l'émulateur ou branchez un équipement réel, lancez l'application, puis observez. Si tout se passe bien (ou plutôt si tout se passe mal), vous devriez voir à l'écran un joli message du style MemeTastic has stopped. Et si vous regardez du côté du Logcat, vous devriez également voir un joli pavé de ce type :
Bravo ! C'est un magnifique plantage. Reste à comprendre pourquoi. Si vous y regardez de plus près, vous pourrez trouver la cause du plantage. La ligne qui nous intéresse pour cela commence par Caused by :
Caused by: java.lang.NullPointerException: Attempt to get length of null array
Nous y découvrons que le plantage est dû à la récupération de la taille d'un tableau. Or ce tableau n'existe pas. Si vous regardez juste en dessous de cette ligne, vous verrez apparaître le contenu de la fameuse pile. Chaque entrée dans la pile apparaît sur une ligne spécifique, qui commence par at. Les appels les plus récents sont en haut et les plus anciens en bas :
at io.github.gsantner.memetastic.activity.MainActivity.onCreate(MainActivity.java:154) at android.app.Activity.performCreate(Activity.java:6980) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6540) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
La première ligne de la pile correspond à l'appel de la méthode onCreate(). Tiens donc, c'est justement dans cette méthode que nous avions commenté une ligne de code ! Pour connaître l'endroit exact du plantage, Android Studio nous aide grandement : il nous indique le nom du fichier et la ligne qui a causé le plantage. Vous pouvez même cliquer dessus pour vous y rendre directement. La ligne incriminée est la suivante :
for (String cat : _tagValues) {
Forcément, nous cherchons à itérer sur le contenu d'un tableau qui n'existe pas ! Car nous avons commenté la ligne permettant de l'initialiser. Dans ce cas précis, nous savons tout de suite d'où vient l'erreur. En temps normal, nous nous serions posé la question suivante : "Pourquoi la variable n'est-elle pas initialisée ?". Il nous aurait suffi de remonter dans le code pour trouver et comprendre l'origine du problème.
En résumé
La pile d’exécution permet d’ordonner les traces afin de remonter à la source si nécessaire.
En plus des traces ordonnées, Android Studio nous indique le fichier et la ligne concernés pour chaque appel.
Les messages d’erreur sont généralement assez explicites. Si ce n’est pas le cas, ayez le réflexe de copier-coller votre erreur sur Google.
Vous savez désormais analyser vos erreurs, découvrons dans le chapitre suivant comment suivre l’évolution de votre application grâce aux points d’arrêt.