Cyril Mottier

“It’s the little details that are vital. Little things make big things happen.” – John Wooden

Utilisation Des Log.x

Comme chacun le sait, la méthode de débuggage probablement la plus commune (quelque soit le langage), est d'utiliser des primitives d'affichage sur la sortie standard. Qui ne se rappelle pas des fameux printf du langage C ou des NSLog de l'Objective-C ? Le langage Java offre également la même possibilité grâce à des méthodes de type print du package System.out.

Le développement sur Android ne déroge pas à la règle puisque le SDK fournit une classe nommée android.util.Log qui inclue un bon nombre de méthodes d'aide au développement données dans la liste ci-dessous :

  • Log.v : Affiche le message en mode “verbose” c'est à dire “verbeux” ou “abondant” en français
  • Log.d : Affiche le message en mode “debug”, utilisé pour le débuggage
  • Log.e : Affiche le message en mode “error” (erreur)
  • Log.w : Affiche le message en mode “warning”, c'est à dire les avertissements
  • Log.i : Affiche le message en mode “info”

L'utilisation de ces méthodes n'est pas à prendre à la légère. En effet, l'utilisation abusive de ces méthodes engendre un ralentissement du temps d'exécution de votre application. De plus, il faut bien prendre en compte les différences entre ces méthodes. Les méthodes v et d ne doivent être utilisées que lors du développement et ne surtout pas être présentes lors de la diffusion de votre programme (sur l'Android Market par exemple). Les méthodes e, w et i peuvent être présente dans une application en “release” mais de façon “limitée” afin de ne pas ralentir inutilement l'application. En conclusion, malgré leur efficacité redoutable (en comparaison d'un débogueur conventionnel), ces méthodes sont à utiliser avec parcimonie et de façon intelligente.

Lorsque vous utilisez cette méthode commune de débuggage, vous êtes confronté à un problème. En effet, malgré l'étonnante efficacité de ce type de débuggage, lorsque vous souhaiter passer votre application en “release” (c'est à dire que vous êtes sur le point de la diffuser), vous devez supprimer l'ensemble des méthodes Log.x. Malheureusement, seule une recherche manuelle suivie d'une suppression des lignes contenant les Log.x est possible. Cette démarche est longue et sujette à erreur. Je vais donc vous présenter quelques astuces pour contourner le problème.

Une astuce pour supprimer facilement les Log.x

Lorsque vous souhaitez supprimer tous les appels à ces méthodes d'affichage, il peut être assez pénible de rechercher toutes les occurrences à Log.x. Une technique consiste à utiliser le modèle suivant :

MyClass.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.cyrilmottier.android.test;

import android.util.Log;

public class MyClass {

    // Le tag affiché pour les logs de la classe
    private static final String LOG_TAG = "MyClass";

    // true si on souhaite afficher les logs de cette classe sinon false
    private static final boolean LOGV = true;

    // Config.LOGV est une variable publique statique finale relative au
    // projet global. Ainsi si cette variable est à true, l'ensemble
    // des logs du projets sont affichés.
    private static final boolean isLogged = LOGV || Config.LOGV;

    public MyClass() {
        log("Constructeur");
    }

    private void log(String log) {
        if (isLogged) {
            Log.d(LOG_TAG, log);
        }
    }
}

L'utilisation de cette astuce consiste en réalité à “exporter” l'affichage des logs dans une méthode externe : log. Cette méthode n'affiche les logs que si au moins une des deux variables LOGV ou Config.LOGV est à true. L'astuce semble pourtant n'apporter qu'un très faible avantage : celui de ne pas avoir à réécrire Log.d(LOG_TAG,. Eh bien en fait ce n'est pas tout ! Si on utilise un décompilateur sur le bytecode Java, on se rend compte que le compilateur supprime purement et simplement le bloc conditionnel if (isLogged) { ... } si la variable isLogged est à false. Cela provient du fait que la variable isLogged est déclarée en static final (constante). Le compilateur “comprend” donc que le bloc conditionnel est inutile et “transforme” alors la méthode log(String log) en une méthode vide.

Pour conclure, cette astuce se résume de la façon suivante :

  • Si isLogged est à true (c'est à dire qu'une des deux variables LOGV ou Config.LOGV est à true) alors le compilateur génère la méthode log(String log) de la façon donnée ci-dessous. Cela revient donc à afficher les logs par une indirection (appel à log au lieu de Log.x directement).
1
2
3
4
5
private void log(String log) {
    if (isLogged) {
        Log.d(LOG_TAG, log);
    }
}
  • Si isLogged est à false, le compilateur “vide” totalement la méthode log(String). Votre programme fait alors des appels à une méthode vide. Le temps perdu pour ces appels inutiles est négligeable en comparaison du lancement d'une Activity vide par exemple - ratio de l'ordre 15 pour 3 000 000.
1
2
private void log(String log) {
}

C'est pratique mais attention !

Cette astuce comporte néanmoins une contrepartie. En effet, elle n'empêche pas la création d'objets même si ces derniers sont inutiles. Prenons l'exemple suivant en supposant que isLogged est à false (et donc que la méthode log(String) est vide) :

1
2
3
public void myMethod(int i) {
    log("index = " + i);
}

Si on compile puis décompile ce code, on s'aperçoit que le code résultant est le suivant :

1
2
3
public void myMethod(int i) {
    log((new StringBuilder("index = ")).append(i).toString());
}

Le code généré créé donc un nouvel objet StringBuilder auquel on ajoute l'entier i. Le compilateur n'a pas été assez intelligent pour “deviner” que cette portion de code n'était pas nécessaire et instancie alors des objets inutiles. Cette création d'objets s'accompagne alors d'un possible passage du ramasse-miettes et d'un ralentissement hypothétique de votre programme.

Conclusion

La méthode “ultime” pour éviter de devoir supprimer les Log.x serait d'inclure le contenu de la méthode log(String) dans votre code. Le compilateur supprimera alors purement et simplement les bloc conditionnel si isLogged est à false. De cette façon, il n'y a aucune instanciation de StringBuilder. Cela implique que votre code compilé est parfaitement sain mais votre code source un peu plus lourd :

1
2
3
4
5
public void myMethod(int i) {
    if (isLogged) {
        Log.d(LOG_TAG, "index = " + i);
    }
}

Les astuces que je viens de vous donner ont leurs avantages et leurs désavantages. Je pense que les éléments donnés ici vous permettront de mieux appréhender ce que fait le compilateur et d'en déduire la méthode la plus appropriée dans votre cas. Bon débuggage ! ;).