Comment optimiser une application Android – Partie 1

Le but de ce tutoriel est de présenter quelques bonnes pratiques et/ou optimisations que vous pouvez appliquer à vos applications.
Cela sera abordé a travers plusieurs aspects.

Les différentes optimisations prisent séparément peuvent vous paraître ridicule, mais chaque erreur d’optimisation x 100000 dans une application peut entrainer des ralentissements, fuites mémoires ou autres.

1 – Allocations d’objets

L’allocation d’objets est UN POINT sensible de l’optimisation d’une application. Plus vous allouez d’objet, plus votre application sera gourmande en mémoire.

Voici quelques règles à respecter dans l’allocation de mémoire :

Vous n’allouerez pas d’objet d’objet dans les boucles

Lorsque vous êtes dans une boucle (for / while …), évitez d’allouer des objets à l’intérieure de la boucle. Car cela va entrainer la création d’un nouvel objet à chaque itération. Si votre boucle par exemple, possède 100 itération, vous allez créer 100 objets inutilement. Pensez plutôt à initialiser l’objet en amont et le mettre à jour le moment voulu.

Privilégiez les boucles for simple

Privilégiez les boucles for simple au foreach (for (Object o : list)) car le foreach entraine l’initialisation d’un objet Iterator (servant à parcourir la liste).

//avant
for (Object o : list) {
   // code dans la boucle
}

//apres
int size = list.size();
Object obj;
for (int pos = 0; pos < size; ++pos) {
    obj = list.get(pos);
    //reste du code
}

Attention au vue personnalisée

Quand vous créez des vues personnalisées et que vous surchargez la méthode onDraw (permettant de dessiner votre vue). Eviter de créer des objets dans la méthode onDraw (Paint, Canvas …). Privilégié la création d’un objet global que vous mettrez a jour si besoin. Sinon à chaque passage dans la méthode onDraw (possiblement plusieurs fois pour un élément), l’objet en question sera alloué.

Utilisez les types primitifs (int, float)

Préférer utiliser les types primitifs (int, float, double) au lieu des classes (Integer, Float, Double), car les classes non primitif vont entrainer une création d’un objet. Cela est valable aussi dans les paramètre d’une méthode. Par exemple :

 int i = 42;
 myMethod(i);

 private void myMethode(Integer i) {}

Si vous regardez la méthode “myMethod”, elle prends en paramètre un Integer. Si vous utilisez la méthode avec un type primitif int, cela va créer un objet Integer pour l’autoboxing. Donc cette méthode va créer un objet inutilement alors qu’elle peut très bien prendre en paramètre un int.
Si cette méthode est appelée très fréquemment dans votre application et que cette erreur se produit souvent dans votre code, comptez le nombre d’objet qui sera créée inutilement.

Attention aux collections

Evitez les HashMap

Si votre application contient des HashMap<T1, T2> afin de pouvoir stocker un couple de données clé/valeur. L’élément T1 et T2 sont deux objets qui représentent le type utilisé respectivement de clé et de valeur a votre HashMap.
Une Hashmap contient forcement un typage objet. Si par exemple vous initialisez une HashMap<int, float> cela est en réalité une HashMap<Integer, Float>.
Pour optimiser cela, Android fourni un ensemble de classe qui surcharge la classe SparseArray.
La classe SparseArray<T> utilise un int comme clé et le type T indiqué en valeur. Cela évite l’autoboxing pour la gestion de la clé de votre collection.
Vous pouvez avoir plus de détail à l’adresse suivante : http://developer.android.com/reference/android/util/SparseArray.html

Plusieurs classes sont disponibles pour le SparseArray dont :

  • SparseBooleanArray : Lie une clé (int) a une valeur (Boolean)
  • SparseIntArray : Lie une clé (int) a une valeur (int)
  • SparseLongArray : Lie une clé (int) a une valeur (long)

Les listes

Si vous utilisez des listes dans votre application, pensez à initialiser la liste avec une taille si cela est possible. ça évite de gérer une liste avec une taille dynamique et donc d’allouer des nouvelles entrées quand vous ajoutez un élément. Voici un petit exemple :

 //Avant
ArrayList<String> myList = new ArrayList<>();
for (int i = 0; i < 100; ++i)
{
   myList.add("toto");
}

//Après
ArrayList<String> myList = new ArrayList<>(100);
for (int i = 0; i < 100; ++i)
{
    myList.add("toto");
}

Ne pas abusez des variables statiques

Les variables statiques sont très pratique mais il faut faire attention à leur utilisation (ce n’est pas un système de cache ou stockage). Cela peut avoir comme conséquence qu’une variable statique utile dans un endroit donné de l’application reste allouée tout au long du process d’une application.

Pensez à désactiver vos listeners (GPS par exemple), services lorsque cela est plus utile (passage de l’application en background par exemple)

J’espère que ces pistes d’optimisations vous seront utiles. N’hésitez pas à poser toutes vos questions dans les commentaires.

16 commentaires


  1. @Nazim,

    Dans ton article tu peux ajouter les problèmes de fuites mémoire lié à l’utilisation des AsynTask.

    Tu parles des fuites de mémoires mais, tu nous donne aucun exemple pour les illustrer. 🙂

    Deeptha

    Répondre

  2. Slt tout le monde ! Au fait, je voudrais savoir est-ce qu’est-il possible de créer une application mobile Android pour les sms dont l’envoi des messages ne depend pas du forfait des sms mais celui d’internet ? Donc, autrement dit, envoyer à tout numéro de téléphone pour n’importe quel opérateur téléphonique. SVP veillez m’aider.

    Répondre

  3. Bonjour,

    Cet exemple n’est pas très judicieux :
    //Après optimisation
    int i = 0;
    String myObj = new String();
    while (i < 100) {
    myObj = "monTexte " + i;
    ++i;
    //Suite de votre traitement
    Log.e(TAG, "value = " + myObj);
    }

    L’objet String (“monTexte ” + i) est tout de même créé à chaque itération (et c’est nécessaire), et un objet inutile vide est créé avant la boucle.
    Il n’y a aucune optimisation, la ligne “String myObj = new String()” était inutile dans l’exemple non optimisé, elle le reste dans celui-ci et peut être remplacée par “String myObj;”.

    Au lieu de String on pourrait utiliser par exemple Encoder, et faire un encoder.encode (“some data” + i); et créer l’instance dans la boucle pour l’exemple non optimisé. Ça a plus de sens car cette fois on déporte réellement l’instanciation de l’objet. 🙂

    Répondre

    1. Effectivement. Merci pour ton retour 🙂

      L’erreur sera corrigé très rapidement et un exemple plus pertinent sera utilisé 🙂

      Répondre


  4. Par contre je n’ai pas compris pour les listes.

    À quoi cela sert de leur donner une taille prédéfinis, autant utiliser un tableau normal, non ?

    Répondre

    1. Si tu as le choix, il vaut mieux utiliser un tableau effectivement.

      Le faite de fixer une taille, permet à votre liste de pas être dynamiquement redimensionner a chaque nouvel ajout et donc éviter des traitements inutiles.

      Répondre

Laisser un commentaire

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