Lorsqu'un composant d'application démarre et que l'application n'a aucun autre composant en cours d'exécution, le système Android démarre un nouveau processus Linux pour l'application avec un seul thread d'exécution. Par défaut, tous les composants de la même application s'exécutent dans le même processus et le même thread, appelé thread principal.
Si un composant d'application démarre et qu'il existe déjà un processus pour cette application (car un autre composant de l'application a déjà démarré), le composant démarre dans ce processus et utilise le même thread d'exécution. Toutefois, vous pouvez faire en sorte que différents composants de votre application s'exécutent dans des processus distincts, et vous pouvez créer des threads supplémentaires pour n'importe quel processus.
Ce document explique le fonctionnement des processus et des threads dans une application Android.
Processus
Par défaut, tous les composants d'une application s'exécutent dans le même processus, et la plupart des applications ne modifient pas ce comportement. Toutefois, si vous devez contrôler le processus auquel appartient un composant donné, vous pouvez le faire dans le fichier manifeste.
L'entrée de manifeste pour chaque type d'élément de composant (<activity>, <service>, <receiver> et <provider>) est compatible avec un attribut android:process qui peut spécifier un
processus dans lequel le composant s'exécute. Vous pouvez définir cet attribut de sorte que chaque composant s'exécute dans son propre processus ou que certains composants partagent un processus, tandis que d'autres non.
Vous pouvez également définir android:process de sorte que les composants de différentes applications s'exécutent dans le même processus, à condition que les applications partagent le même ID utilisateur Linux et soient signées avec les mêmes certificats.
L'élément <application>
est également compatible avec un attribut android:process, que vous pouvez utiliser pour définir une
valeur par défaut qui s'applique à tous les composants.
Android peut décider d'arrêter un processus à un moment donné, lorsque d'autres processus qui servent l'utilisateur plus immédiatement ont besoin de ressources. Les composants d'application qui s'exécutent dans le processus arrêté sont donc détruits. Un processus est redémarré pour ces composants lorsqu'ils ont du travail à effectuer.
Lorsque le système Android décide des processus à arrêter, il évalue leur importance relative pour l'utilisateur. Par exemple, il arrête plus facilement un processus hébergeant des activités qui ne sont plus visibles à l'écran qu'un processus hébergeant des activités visibles. La décision d'arrêter un processus dépend donc de l'état des composants qui s'exécutent dans ce processus.
Les détails du cycle de vie des processus et de sa relation avec les états de l'application sont abordés dans la section Processus et cycle de vie des applications.
Threads
Lorsqu'une application est lancée, le système crée un thread d'exécution pour l'application, appelé thread principal. Ce thread est très important, car il est chargé de distribuer les événements aux widgets d'interface utilisateur appropriés, y compris les événements de dessin. Il s'agit également presque toujours du thread dans lequel votre application interagit avec les composants des packages android.widget et android.view du kit d'interface utilisateur Android.
Pour cette raison, le thread principal est parfois appelé thread UI. Toutefois, dans des circonstances particulières, le thread principal d'une application peut ne pas être son thread UI. Pour en savoir plus, consultez Annotations
de thread.
Le système ne crée pas de thread distinct pour chaque instance d'un composant. Tous les composants qui s'exécutent dans le même processus sont instanciés dans le thread UI, et les appels système à chaque composant sont distribués à partir de ce thread. Par conséquent, les méthodes qui répondent aux rappels système, telles que onKeyDown() pour signaler les actions de l'utilisateur ou une méthode de rappel de cycle de vie, s'exécutent toujours dans le thread UI du processus.
Par exemple, lorsque l'utilisateur appuie sur un bouton à l'écran, le thread UI de votre application distribue l'événement tactile au widget, qui définit à son tour son état enfoncé et publie une requête d'invalidation dans la file d'attente des événements. Le thread UI met en file d'attente la requête et informe le widget qu'il doit se redessiner.
À moins que vous n'implémentiez correctement votre application, ce modèle à thread unique peut entraîner de mauvaises performances lorsque votre application effectue un travail intensif en réponse à l'interaction de l'utilisateur. L'exécution d'opérations longues dans le thread UI, telles que l'accès au réseau ou les requêtes de base de données, bloque l'ensemble de l'interface utilisateur. Lorsque le thread est bloqué, aucun événement ne peut être distribué, y compris les événements de dessin.
Du point de vue de l'utilisateur, l'application semble cesser de répondre. Pire encore, si le thread UI est bloqué pendant plus de quelques secondes, la boîte de dialogue "L'application ne répond pas" (ANR) s'affiche. L'utilisateur peut alors décider de quitter votre application, voire de la désinstaller.
N'oubliez pas que le kit UI Android n'est pas thread-safe. Par conséquent, ne manipulez pas votre interface utilisateur à partir d'un thread de travail. Effectuez toutes les manipulations de votre interface utilisateur à partir du thread UI. Le modèle à thread unique d'Android comporte deux règles :
- Ne bloquez pas le thread UI.
- N'accédez pas au kit UI Android en dehors du thread UI.
Threads de travail
En raison de ce modèle à thread unique, il est essentiel pour la réactivité de l'interface utilisateur de votre application de ne pas bloquer le thread UI. Si vous avez des opérations à effectuer qui ne sont pas instantanées, veillez à les effectuer dans des threads d'arrière-plan ou de travail distincts. N'oubliez pas que vous ne pouvez pas mettre à jour l'interface utilisateur à partir d'un thread autre que le thread UI ou principal.
Pour vous aider à suivre ces règles, Android propose plusieurs façons d'accéder au thread UI à partir d'autres threads. Voici une liste de méthodes qui peuvent vous aider :
Les exemples suivants montrent comment décharger une tâche sur un thread d'arrière-plan et mettre à jour le thread UI une fois la tâche terminée :
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Cette implémentation est thread-safe, car l'opération en arrière-plan est effectuée à partir d'un thread distinct, tandis que ImageView est toujours manipulé à partir du thread UI.
Toutefois, à mesure que la complexité de l'opération augmente, ce type de code peut devenir compliqué et difficile à gérer. Pour gérer des interactions plus complexes avec un thread de travail, vous pouvez envisager d'utiliser un Handler dans votre thread de travail pour traiter les messages transmis à partir du thread UI. Pour obtenir une explication complète sur la planification des tâches sur les threads d'arrière-plan et la communication avec le thread UI, consultez la section
Présentation des tâches en arrière-plan.
Méthodes thread-safe
Dans certaines situations, les méthodes que vous implémentez sont appelées à partir de plusieurs threads et doivent donc être écrites pour être thread-safe.
Cela est principalement vrai pour les méthodes qui peuvent être appelées à distance, telles que les méthodes d'un service lié. Lorsqu'un appel sur une
méthode implémentée dans un IBinder provient du même processus dans lequel le
IBinder est en cours d'exécution, la méthode est exécutée dans le thread de l'appelant.
Toutefois, lorsque l'appel provient d'un autre processus, la méthode s'exécute dans un thread choisi à partir d'un pool de threads que le système gère dans le même processus que IBinder.
Elle n'est pas exécutée dans le thread UI du processus.
Par exemple, alors que la méthode
onBind() d'un service est appelée à partir du thread UI du processus du
service, les méthodes implémentées dans l'objet renvoyé par onBind() telles qu'une
sous-classe qui implémente des méthodes d'appel de procédure à distance (RPC), sont appelées à partir de threads
du pool. Étant donné qu'un service peut avoir plusieurs clients, plusieurs threads de pool peuvent engager la même méthode IBinder en même temps. Les méthodes IBinder doivent donc être implémentées pour être thread-safe.
De même, un fournisseur de contenu peut recevoir des requêtes de données provenant d'autres processus.
Les classes ContentResolver et ContentProvider
masquent les détails de la gestion de la communication inter-processus (IPC),
mais les méthodes ContentProvider qui répondent à ces requêtes (les méthodes
query(),
insert(),
delete(),
update(),
et getType()) sont
appelées à partir d'un pool de threads dans le processus du fournisseur de contenu, et non dans le thread UI
du processus. Étant donné que ces méthodes peuvent être appelées à partir d'un nombre quelconque de threads en même temps, elles doivent également être implémentées pour être thread-safe.
Communication inter-processus
Android propose un mécanisme d'IPC à l'aide de RPC, dans lequel une méthode est appelée par une activité ou un autre composant d'application, mais exécutée à distance dans un autre processus, avec un résultat renvoyé à l'appelant. Cela implique de décomposer un appel de méthode et ses données à un niveau que le système d'exploitation peut comprendre, de le transmettre du processus local et de l'espace d'adressage au processus distant et à l'espace d'adressage, puis de réassembler et de réexécuter l'appel.
Les valeurs renvoyées sont ensuite transmises dans le sens opposé. Android fournit tout le code nécessaire pour effectuer ces transactions IPC. Vous pouvez donc vous concentrer sur la définition et l'implémentation de l'interface de programmation RPC.
Pour effectuer une IPC, votre application doit se lier à un service à l'aide de bindService(). Pour en savoir plus, consultez la présentation des services.