Voilà un bon moment que je n’ai pas rédigé d’article sur ce blog. La raison est assez simple et n’est absolument pas dûe à un manque de créativité (loin de là … là dessus je n’ai pour l’instant aucun problème : les idées me sautent sans arrêt à l’esprit ^^) ou tout simplement la feignantise … non le problème est beaucoup plus sérieux puisqu’il s’agit d’un simple mais non moins terrible manque de temps ! Beaucoup auront compris pourquoi : je veux faire trop de choses mais je n’arrive pas à me dédoubler ou mieux me n-doubler !
Après un article plus que complet concernant le tramage sous Android, je souhaitais repartir sur un sujet plus classique et plus succinct traitant d’optimisation. Lorsqu’on apprend le Java, on est rarement confronté aux problèmes de performances puisqu’on apprend, en général, sur un ordinateur. Malheureusement lorsqu’on applique purement et simplement ces enseignements dans le monde du mobile et notamment sur Android, on se retrouve souvent confronté à des problèmes de performances. Le fameux GC n’est pas loin …
L’exemple le plus flagrant est celui de l’utilisation des objets de type “dictionnaire” qui enregistrent les valeurs sous la forme de paires (clés, valeurs). Cette classe, souvent extrêmement utile et irremplacable porte le nom de HashMap<K, V>. L’utilisation d’une telle classe implique malheureusement d’utiliser des objets comme clé ET comme valeur. Cela signifie que si vous souhaitez avoir une HashMap<int, String> vous aurez en fin de compte une HashMap<Integer, String> dans laquelle le type Integer est un objet qui contient un unique int (méthode du boxing).
Etant multi-compétence, Android et iPhone, je rencontre souvent des gens qui découvrent le monde Android et n’hésite pas à faire des HashMap un peu partout en lieu et place des traditionnels NSDictionnary. Il s’avère que la programmation sous iPhone est très différente puisque le choix de la classe à utiliser est très restreint : NSArray pour un tableau, NSDictionnary pour un dictionnaire, etc. En réalité, développer sous iPhoneOS avec des NSDictionnary ne pose pas autant de problème puisqu’il n’y a pas de notion de GC (la ressource est libérée instantanément). En conséquence le boxing/unboxing ne coûte relativement rien.
Prenons MetroMap à titre d’exemple : cette application contient, vous vous en douter, un cache de Bitmaps. Ce dernier associe simplement des Bitmap (valeurs) à des int (clé représentant l’identifiant du Drawable - R.drawable.XXX - utilisé pour la tuile). Pour éviter la création inutile d’objets de type Integer j’ai donc intelligemment (bah oui je fais pas des trucs débiles !) préféré utiliser la classe SparseArray :
package com.cyrilmottier.android.metromap.adapter; import java.lang.ref.SoftReference; import android.util.SparseArray; public class EfficientCache<T> implements Cache<T> { private SparseArray<SoftReference<T>> mCache; public EfficientCache(int cacheSize) { mCache = new SparseArray<SoftReference<T>>(cacheSize); } public T get(int key) { final SoftReference<T> softRef = mCache.get(key); return (softRef == null) ? null : softRef.get(); } public void put(int key, T element) { mCache.put(key, new SoftReference<T>(element)); } } |
Cette classe vous permet d’éviter le surcoût de création d’objets inutiles (les clés) à moindre “frais”. Lorsque cela est possible (c’est à dire lorsque vos clés sont des int, je vous conseille vivement de préférer cette classe à la basique et traditionnelle HashMap<K, V>
Le framework Android fournit dans android.util, un ensemble de 3 classes qui peuvent être utilisées en lieu et place d’HashMap<K, V>:
SparseArraypermet de mapper des objets avec desint. Cela revient donc à un “vrai”HashMap<int, V>SparseIntArray: à l’instar deSparseArray, cette classe fait correspondre desintavec desint. C’est donc similaire (en terme de fonctionnalités) àHashMap<int, int>SparseBooleanArray: Revient àHashMap<int, boolean>
A votre tour d’optimiser vos programmes et de supprimer les HashMaps inutiles.
petite faute : “et très restreint”
“préféré utilisé” -> “préféré utiliser” (je crois)
Sympa comme classe
Mais elle est spécifique Android !
Dommage que ça ne soit pas plus mis en valeur dans la documentation Android…
@Pierre : Merci j’ai corrigé les fautes
Merci ! … Au début, je voulais intituler cet article “Le Java made Android” … mais je me suis dit que c’était pas vraiment parlant. Comme tu le dis c’est propre à Android parce qu’on est sur un environnement où la HashMap est un peu lourde. Dans un contexte de serveur ou desktop c’est pas forcément justifié d’utiliser ce genre de classes.
Merci pour cet article, Cyril. C’est bon à savoir, pour economiser facilement de la memoire. A remarquer aussi l’exemple d’utilisation des SoftReferences …
Euh pourquoi ne pas tout simplement utiliser un ArrayList ?
@pol: Bien sur qu’il est possible d’utiliser un ArrayList dans le cas où les clés sont contiguës. Imagine maintenant que tes clés soient comprises entre 0 et 123456789 …. faire un ArrayList d’une taille de 123456789 objets est quelque peu inutile et surtout extrêmement coûteux en terme de mémoire …
Pour finir, j’ai parlé dans cet article de la notion de “dictionnaire” : c’est à dire d’ensemble de paires (clé, valeur) et non pas de tableaux. Il faut bien comprendre la différence entre ces deux notions.
Très bon article !
Juste une petite question, je fais une application Gps (sous WinCE) et je viens de recevoir un sample Android (le fameux MiD560 de SMiT) et je suis vraiment impatient de porter l’application sur Android ^^
J’aimerai savoir comment tu as fait pour la gestion des Carreaux ? Sur WinCE j’utilise le même procédé que GMap à savoir des carreaux de 256×256, mais j’ai quand même des problèmes de rafraichissement… pas de double buffering sur le Compact Framework 3.5…
Ps : Si vous avez la moindre question sur le MiD560, n’hésitez pas :°)
@Dami : Merci (encore !).
Concernant la “gestion des carreaux” … euh … tu parles de la gestion des “tuiles” sous MetroMap? J’ai pas le temps d’open sourcer et de faire ça bien
Pour résumer, c’est probablement la même technique que GGMaps : des tuiles de 200×200 (dans mon cas). Je peux vraiment rien te promettre concernant la mise en ligne d’un code source !
J’imagine que voir un terminal un peu innovant donne des envies
me n-doubler !
Demande a Naruto
Je vais me servir de ce que tu montres dans ton articles !!!
Mais je ne m’y attendais vraiment pas.
Ca va me servir à traduire les keyevent d’android en keyevent de swing.
Mais j’utilise SparseIntArray.
PS : tu ne peux pas activer une notification par mail pour les nouveaux commentaires sur ton blog ?
@Pierre : je viens d’ajouter un plugin qui permet (normalement) d’être notifié si tu le souhaites. Si tu as envie de tester n’hésite pas et surtout n’oublie pas de me dire si ça marche.
(Testons voir si ça marche)
En fait je ne vais pas me servir de SparseArray, car j’ai choisi finalement de ne pas implémenter une fonctionnalité sous Android, mais directement en java
@Pierre : ça semble fonctionner parfaitement :). Je ne vois pas pourquoi tu ne souhaites pas utiliser SparseArray si tu programme une application pour Android… Mais bon tu fais comme tu veux. Mon but était simplement de vous faire connaitre des classes qui optimisent vos applications Android.
j’ai aussi reçu le mail.
tout simplement parce que mon application est décomposé en client/serveur, et que le SparseArray/HashMap m’est finalement utile sur le serveur uniquement (donc java classique)
Recoucou, Comment tu fais pour stocker les coordonnées de tes tuiles dans ton EfficientCache ? Et surtout comment fais-tu pour parcourir ton SparseArray pour détecter les éléments qui doivent être supprimés (vu qu’ils sont plus affichés à l’écran ?
Parce que moi je suis obligé d’utiliser un HashMap pour les coordonnées, et je vois pas trop comment parcourir ce dernier pour trouver les correspondances avec les coordonnées du MotionEvent…
(Surtout que maintenant que j’ai lu cette article, je sais que c’est possible sans HashMap et du coup c’est super frustrant :°)
@Dami : Tu devrais vraiment télécharger l’application pour mieux comprendre comment elle fonctionne. Il n’y a pas de notions de coordonnées géographiques dans MetroMap. Par contre il y a bien une notion de coordonnées dans la matrice des tuiles. Ces coordonnées (ligne, colonne) sont en fait enregistrée dans un objet Tile qui contient ligne, colonne et Bitmap de la tuile.
La technique pour supprimer les tuiles n’a rien à voir avec le SparseArray. Il faut simplement parcourir l’intégralité de tes tuiles : si le rectangle englobant n’intersectionne pas le rectangle visible alors la tuile n’a pas besoin d’être affichée.
J’espère t’avoir aider :).