Lors de la précédente partie de cette série, nous avons introduit la notion de programmation concurrente sous Android. Nous avons, par exemple, découvert les notions d'ANRs. Il a été fait mention des raisons de l'apparition de ces boites de dialogues et de la nécessité de faire en sorte qu'elles ne s'affichent jamais dans vos applications. Malgré l'absence de lignes de code dans la précédente partie, il est important de bien avoir compris les notions qui y ont été introduites. Si vous n'avez pas eu l'occasion de vous plonger dans la partie 1, je vous conseille donc vivement de la lire avec attention à la page suivante :
Android et la programmation concurrente - Partie 1
Le système de message Android
Android facilite énormément la programmation inter-thread grâce à un ingénieux système de messages. Etant particulièrement attaché à cette notion j'ai souhaité la présenter comme première alternative à la programmation concurrente sous Android (pour ne rien vous cacher, c'est à mon sens la meilleure technique pour résoudre ce genre de problématiques sous Android)
Définitions
Thread
: Souvent appelés processus légers, les threads peuvent être considéré comme des tâches dans lesquels s'exécutent des instructions. Dès lors qu'une application Android démarre, un thread est créé (le main thread) et sert de réceptacle pour les instructions du système. Pour donner une illusion de parallélisme, il est possible de créer plusieurs threads dans une application. Chacun des threads exécute de façon alternative (mais rapide d'où l'impression de parallélisme ou de simultanéité) des instructions. Le schéma ci-dessous montre une application disposant de 3 threads :
Looper
: LeThread
est la base de la programmation concurrente … Malheureusement utilisé tel quel il n'a que peu d'utilité. En effet si on regarde attentivement le schéma précédent, on se rend compte qu'un thread s'arrête dès lors qu'il a fini d'exécuter ses instructions. Pour éviter cela, il est possible de faire en sorte qu'il boucle indéfiniment en attente d'instructions à exécuter. On met alors en place unLooper
qu'on appelle parfois également “boucle évènementielle”. La classeLooper
permet de préparer unThread
à la lecture répétitive d'actions. Un telThread
, présenté dans la figure ci-dessous, est souvent appelé looper thread. Sous Android, le main thread est en réalité un looper thread. UnLooper
étant propre à un uniqueThread
, il est implémenté sous la forme du design pattern TLS ou Thread Local Storage (les plus curieux pourront jeter un oeil à la classeThreadLocal
dans la documentation Java ou Android).
Message
: UnMessage
représente une ou un ensemble de commandes à exécuter. Dans la définition précédente, leMessage
fait donc office d'instructions.Handler
: Cette classe vous permet d'interagir avec les looper threads. C'est par le biais d'unHandler
qu'il vous sera possible de poster desMessage
s ou desRunnable
s dans leLooper
qui seront exécutés (au plus vite, après un temps donné ou à un moment donné) par le thread looper pointé par leHandler
(bravo à ceux qui ont compris cette phrase bourrée de vocabulaire propre à Android en une seule et unique lecture). Le code ci-dessous permet de bien comprendre l'utilisation duHandler
. Ce code consiste à créer uneActivity
se terminant après un laps de temps définit.
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
HandlerThread
: Créer un thread looper via code est une tâche qui peut s'avérer répétitive et sujette à erreur. Pour vous éviter de créer vous même des threads disposant d'unLooper
, le framework Android propose la classeHandlerThread
. Contrairement à ce que laisse penser le nom de cette classe, unHandlerThread
n'associe aucunHandler
auLooper
.
Module de téléchargement d'images
Fort des définitions précédentes, nous sommes maintenant parés pour développer un petite module de téléchargement d'images sur Internet. Pour ce faire nous allons concevoir un système téléchargeant des images en tâche de fond. Vous trouverez l'intégralité du code de l'application dans le zip à l'adresse suivante:
Plutôt que de longs discours, j'ai préféré commenter le code de la classe HandlerThreadImageLoader
. Bien que la plupart du code qui nous intéresse se trouve dans cette même classe, je vous encourage vivement à jeter un oeil au code de l'application pour mieux comprendre comme utiliser le HandlerThreadImageLoader
.
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
|
Optimisations
Exécuter des opérations en tâche de fond ne signifie pas qu'il vous est possible de faire tout et n'importe quoi sans impacter les performances du terminal. Il ne faut jamais oublier que le code que vous écrivez s'exécute sur des machines généralement peu puissantes. A ce titre, voici quelques éléments et optimisations supplémentaires fournis “tel quel”.
A la lecture de cet article, on se rend compte que la classe Message
est extrêmement importante. Les instances de cette classe peuvent potentiellement être nombreuses. Pour éviter de créer énormément d'instances (comprendre faire des new Message()
), il est possible de réutiliser les Message
n'ayant plus d'utilité. Je vous recommande donc vivement de récupérer un Message
en faisant appel aux méthodes suivantes :
Message.obtain()
: Permet de récupérer unMessage
du pool global ou d'en créer un nouveau si aucunMessage
n'est disponible dans le pool.Handler.obtainMessage()
: Cette méthode est similaire à la précédente mais associe directement leMessage
auHandler
courant.
Beaucoup de gens pensent, à tort, qu'un Service s'exécute dans un Thread
secondaire. En réalité, un Service
s'exécute, au même titre qu'une Activity
ou un BroadcastReceiver
dans le main thread. Pour éviter de bloquer inutilement le main thread il peut donc être judicieux de démarrer un HandlerThread
. La classe IntentService
remplit parfaitement ce rôle :
Au premier appel de
startService(Intent)
, leService
démarre et initialise unHandlerThread
L'
Intent
passé à la méthodestartService(Intent)
est ensuite transféré à la méthodeonHandleIntent(Intent)
qui s'occupe de traiter la demande exprimée par l'Intent
Si aucun autre appel à
startService(Intent)
n'a été fait avant la fin deonHandleIntent(Intent)
, leService
s'arrête.Si d'autres appels à
startService(Intent)
sont effectués, lesIntent
s “s'empilent” et seront gérés lors d'un prochain tour de boucle duLooper
L'IntentService
est particulièrement adapté aux requêtes réseau. En effet, il permet de traiter les demandes les unes après les autres et s'arrête de lui même lorsqu'il ne reste plus aucune requêtes à traiter.
Conclusion
Voilà ! Je pense avoir fait le tour de la question des Handler
s, Looper
s et autre Message
s sur Android. N'hésitez pas à utiliser ce système aussi bien utile pour gérer la communication inter-threads que pour retarder l'exécution d'instructions (postDelayed
).