Android – Utiliser la stacktrace pour vérifier l’initialisation de sa librairie

Partager cet article

Temps estimé pour la lecture de cet article : 20 min

Aujourd’hui, on va s’intéresser à la stracktrace, bien souvent quand on en rencontre une, c’est plutôt négatif car c’est qu’on a rencontré un crash ! Mais saviez-vous, qu’il est également possible de l’utiliser afin de s’assurer du bon fonctionnement d’une librairie.

Prenons un exemple, je développe une librairie utilisée afin de logger le comportement d’une application et j’aimerais pouvoir récupérer ses logs à posteriori. Je pourrais très bien faire mon initilisation dans la méthode onCreate de mon activité mais en la mettant dans la classe Application, je serais certain qu’elle sera initialisée avant tout le reste et je pourrais ainsi voir l’initalisation de mon application.

Si l’enregistrement, n’est pas fait dans le bon onCreate, cela pourrait être embêtant. Grâce à la stracktrace et la classe Throwable, on va pouvoir prévenir l’utilisateur si l’initialisation a été mal faites ! Préparez-vous à ce merveilleux voyage !

Comment débuguer grâce à la stracktrace java

Au début, quand on débute le java, on rencontre souvent des exceptions comme celle-ci :

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

La stack trace, c’est la liste des méthodes que l’application était en train d’appeler au moment où une exception a eu lieu.

Si on regarde la stacktrace du dessus, on peut savoir exactement d’où vient l’erreur. Ici, elle est plutôt simple, on a un NullPointerException. Une erreur assez courante qui veut tout simplement dire qu’on essaye d’accéder à un objet qui n’a pas été initialisé.

En analysant la première ligne après l’exception, on voit que l’erreur se situe à ce niveau là :

at com.example.myproject.Book.getTitle(Book.java:16)

Il suffirait alors d’ouvrir la classe Book.java et de voir pourquoi, l’attribut title de l’objet est nul, surement un champ qu’on a oublié d’instancier dans le constructeur de l’objet Book.

C’est bien beau, tout ça mais en quoi ça nous aide pour notre problème ? Patience, on y vient !

Récupérer la stacktrace d’une application au runtime

Afin d’obtenir la stack trace d’une application à un moment donné t, on peut utiliser deux méthodes :

  • On pourrait récupérer le Thread courant grâce à la méthode statique Thread.currentThread() et ensuite appelée la méthode getStackTrace().
  • Une autre possibilitée, serait de créer une instance de la classe Throwable et appeler la méthode getStackTrace() également.

Ici, on va partir sur la deuxième solution, par exemple :

Throwable throwable = new Throwable();
StackTraceElement[] stackTraceElements = throwable.getStackTrace();

On récupére alors une liste de StackTraceElement, cet objet va grandement nous intéresser car, il possèdes les méthodes suivantes :

  • String getClassName(): nous permet d’obtenir le nom de la classe
  • String getMethodName(): le nom de la méthode appelée

Ainsi, au moment où l’on va exécuter notre bout de code, on va être capable d’avoir l’historique des classes qui ont été appelées, ainsi que le nom des méthodes. Et là, la magie de la réflexion en java rentre en action, voyons pourquoi !

Retour à notre librairie

Notre objectif est simple vérifier que notre singleton permettant d’initialiser notre librairie soit appelé dans la méthode onCreate de la classe Application d’Android et si ce n’est pas le cas, on lancera une exception.

Pour ce faire, on va créer une instance de l’objet Class qui va pointer sur la classe Application. À partir de là, il nous suffira d’obtenir la stack trace courante et de vérifier que pour chaque élément, si la classe courante est héritée de la classe Application et si c’est le cas regarder quelle méthode est appelé.

Pour commencer, nous allons récupérer une instance de la classe courante grâce à la méthode Class.forName. Ensuite, pour vérifier de quelle classe hérite notre instance, nous allons utiliser la fonction isAssignableFrom. Enfin, il nous restera plus qu’a s’assurer que la méthode appelée s’appelle bien « onCreate » ! Ce qui nous donne :

private void checkIfCalledFromApplication() {
    boolean isAllowedToInitialize = false;
    final String methodName = "onCreate";
    final Class className = Application.class;

    final Throwable throwable = new Throwable();
    final StackTraceElement[] stackTraceElements = throwable.getStackTrace();
    for (StackTraceElement stackTraceElement : stackTraceElements) {
        final String stackTraceElementClassName = stackTraceElement.getClassName();
        Class callerClass = null;
        try {
            callerClass = Class.forName(stackTraceElementClassName);
        } catch (ClassNotFoundException e) {
            Log.d(TAG, e.getMessage());
        }
        if (methodName.equals(stackTraceElement.getMethodName())) {
            if (callerClass != null && className.isAssignableFrom(callerClass)) {
                isAllowedToInitialize = true;
            }
        }
    }

    if (!isAllowedToInitialize) {
        final String docUrl = "...";
        throw new RuntimeException("MySingleton must be called from Application.onCreate. Cf: " + docUrl);
    }
}

Il vous suffira alors d’appeler la méthode checkIfCalledFromApplication au moment où votre librairie va s’initialiser. Bien sur au lieu de lancer une exception, on pourrait très bien renvoyer juste un log d’erreur afin d’informer le développeur, qu’il a mal démarré la librairie.

Cela va surtout dépendre de la criticité de votre librairie, si cette dernière pourrait engager la stabilité de l’application, si mal initialisé, mieux vaut lancer une exception ! Que pensez-vous de cette utilisation de la stacktrace, pratique non ?

Have gun guys !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.