Préambule▲
Lorsqu'on me demande mon avis sur la VM Android j'aime utiliser l'expression : « la VM Android est tout ce qu'il y a de plus élémentaire ! ». En effet, à force de jouer avec cette dernière, on se rend compte qu'elle n'est pas ultra performante : c'est une simple VM interprétée. Le garbage collector (GC) n'est pas plus développé puisque c'est un basique GC « mark and sweep » (pas de GC générationnel par exemple). Android n'inclut pas non plus de technologies telles que la compilation JIT (Just In Time) ou les optimisations à la compilation (caching de variables constantes de boucle, etc.) . Pour résumer, la VM Android ressemble un peu aux premières VM Java qui aient existé sur cette Terre ! Elle dispose néanmoins d'un énorme avantage : elle fonctionne :).
Android ne repose donc pas sur une base « ultra » performante. Ainsi le GC prend, en règle générale environ 100 ms à s'exécuter bloquant totalement l'exécution du programme. Cela peut paraitre dérisoire, mais bloquer le thread graphique pendant 100 ms provoque généralement une forme de mécontentement chez certains, voire tous les utilisateurs ^^. Imaginez une image se déplaçant uniformément du point supérieur gauche de l'écran au point inférieur droit en 500 ms. Sur un écran de 320×480 pixels, l'image parcourt environ (comme quoi Pythagore est toujours utile) sqrt(320^2 + 480^2) ? 577 pixels en 500 ms soit environ 115 pixels toutes les 100 ms. Dans le cas où le GC s'exécute durant l'animation, un blocage se fait ressentir. Votre image va tout simplement « sauter » 115 pixels faisant croire à une cassure de l'animation.
Lorsque vous développez votre interface graphique, vous devez faire en sorte que cette dernière soit la plus fluide possible. Rendre une interface graphique fluide plusieurs techniques dont la principale consiste à empêcher le GC de s'exécuter. Malheureusement pour les développeurs d'UI, le GC est une composante principale du Java. Il n'est pas possible de le supprimer. L'astuce consiste donc à le contourner en n'allouant/désallouant aucune ressource lorsqu'une animation (scrolling de liste, de menu, animation basique, etc.) est en cours. L'intérêt de cet article est de montrer certaines techniques permettant de satisfaire la précédente astuce.
Créer les objets au plus tôt▲
Il m'arrive régulièrement de lire du code ressemblant au suivant :
@Override
protected
void
onDraw
(
Canvas canvas) {
super
.onDraw
(
canvas);
Â
// ...
Â
Paint paint =
new
Paint
(
);
paint.setColor
(
Color.BLACK);
canvas.drawText
(
mText, 20
, 20
, paint);
Â
//...
}
Créer l'objet de type Paint dans la méthode onDraw(Canvas) (méthode considérée comme « critique » puisqu'elle est appelée très souvent) ralentit extrêmement l'exécution du programme et provoque des GC lorsque trop d'objets de type Paint ont été créés. Préparer les objets (Paint, Rect, Runnable, etc.) dès la création de l'instance est bien souvent la seule chose à faire pour résoudre ce problème :
public
class
MyView extends
View {
private
final
Paint mPaint =
new
Paint
(
);
public
MyView
(
Context context) {
mPaint.setColor
(
Color.BLACK);
// ...
}
Â
@Override
protected
void
onDraw
(
Canvas canvas) {
super
.onDraw
(
canvas);
Â
// ...
canvas.drawText
(
mText, 20
, 20
, mPaint);
//...
}
Â
}
Maîtrisez l'autoboxing/unboxing▲
Java 1.5 inclut une fonctionnalité qui facilite grandement le développement : l'auto-boxing/unboxing des types primitifs. Prenons l'exemple ci-dessous :
private
HashMap<
Integer, String>
mHashMap;
public
MyObject
(
) {
mHashMap =
new
HashMap<
Integer, String>(
);
mHashMap.put
(
1
, "My String 1"
);
mHashMap.put
(
40
, "My String 2"
);
mHashMap.put
(
567
, "My String 3"
);
}
Ajouter de nouveaux couples (clé, valeur) à notre HashMap se fait de façon déconcertante puisque Java utilise le type primitif int comme clé. Comment cela est-il possible puisque les clés ne peuvent normalement être que des objets (dans notre cas de type Integer). En réalité, Java 1.5 crée un objet de type Integer ne contenant qu'un int. C'est ce qu'on appelle l'auto-boxing. Malheureusement, cela signifie que Java crée des objets « inutilement ».
Pour contrer ce problème, essayez de créer vos propres structures de données à base de tableaux élémentaires (oui oui je parle des tableaux avec les [] et non pas de LinkedList, Vector ou autre ArrayList). Dans l'exemple ci-dessus, il est également possible d'utiliser un objet extrêmement pratique disponible dans android.util : SparseArray. Il permet de « mapper » des objets à des int (servant de clés) même s'il existe des gaps entre les clés.
Attention aux Strings▲
Le type String est un type un peu spécial en Java. En effet, ce n'est pas réellement un type primitif, mais le langage l'intègre tellement bien qu'il s'utilise comme tel. En réalité, les objets de type String sont « immutables ». Cela signifie qu'ils ne peuvent tout simplement pas être modifiés. L'exemple suivant implique donc une création de nouvelles Strings à chaque tour de boucle :
private
String mStrings[];
Â
@Override
public
String toString
(
) {
final
int
count =
mStrings.length;
String res =
""
;
for
(
int
i =
0
; i <
count; i++
) {
res +=
mStrings[i];
}
return
res;
}
L'utilisation d'objets « mutables » tels que StringBuilder accélère grandement l'exécution du programme en minimisant la création d'objets :
private
String mStrings[];
@Override
public
String toString
(
) {
final
int
count =
mStrings.length;
StringBuilder builder =
new
StringBuilder
(
);
for
(
int
i =
0
; i <
count; i++
) {
builder.append
(
mStrings[i]);
}
return
builder.toString
(
);
}
Réutilisez les objets inutiles▲
La dernière règle élémentaire qui, de mon point de vue, est LA règle à ne pas oublier sur terminaux mobiles se résume en un mot : Réutilisez ! Lisez bien la documentation Android, car certaines méthodes comme View getView (int, View, ViewGroup) de la classe Adapter fournissent des objets à réutiliser. Dans le cas d'une liste par exemple, dans laquelle chaque cellule est une View, il est beaucoup plus rapide de modifier le contenu de la vue que de créer une nouvelle View « from scratch » (ce qui implique généralement de faire un « inflate » d'une ressource XML – démarche longue et douloureuse pour le terminal).
Je pense avoir donné ici les principaux points qui me sont venus à l'esprit au moment de la rédaction de cet article. J'aurais aimé en écrire plus sur ce domaine, mais je crois plus raisonnable de laisser une seconde partie pour de futurs articles. Je ne peux maintenant que vous souhaitez « bon codage » ! Codez bien, codez avec votre tête.
Remerciements▲
Un grand merci à jpcheck pour sa relecture.