Présentation des processus et des threads

Lorsqu'un composant d'application démarre et qu'aucun autre composant ne s'exécute dans l'application, le système Android lance 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 dans le même thread, appelé thread main.

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é, il commence 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 créer des threads supplémentaires pour tout 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 paramètre. Toutefois, si vous souhaitez contrôler le processus auquel appartient un composant donné, vous pouvez le faire dans le fichier manifeste.

L'entrée du fichier manifeste pour chaque type d'élément de composant (<activity>, <service>, <receiver> et <provider>) accepte 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 pour que certains composants partagent un processus et 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> accepte également 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 des ressources sont requises par d'autres processus qui servent plus immédiatement l'utilisateur. Les composants d'application exécutés dans le processus arrêté sont par conséquent détruits. Un processus est relancé pour ces composants lorsqu'il y a du travail à effectuer.

Pour décider des processus à arrêter, le système Android en évalue l'importance relative pour l'utilisateur. Par exemple, elle 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 ou non un processus dépend donc de l'état des composants exécutés dans ce processus.

Les détails du cycle de vie du processus et de sa relation avec les états d'application sont abordés dans la section Processus et cycle de vie de l'application.

Fils de discussion

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 UI Android. Pour cette raison, le thread principal est parfois appelé thread UI. Toutefois, dans des circonstances spéciales, 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 du 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 envoie l'événement tactile au widget, qui définit à son tour l'état d'appui et publie une requête d'invalidation dans la file d'attente des événements. Le thread UI retire la requête de la file d'attente et demande au widget de se redessiner.

À moins d'implémenter correctement votre application, ce modèle à thread unique peut générer de mauvaises performances lorsque votre application effectue un travail intensif en réponse à une interaction utilisateur. L'exécution d'opérations de longue durée 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'UI. Lorsque le thread est bloqué, aucun événement ne peut être envoyé, y compris les événements de dessin.

Du point de vue de l'utilisateur, l'application semble se bloquer. Pire encore, si le thread UI est bloqué pendant plus de quelques secondes, une 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. Veillez donc à ne pas manipuler votre UI à partir d'un thread de nœud de calcul. Effectuez toute la manipulation de votre interface utilisateur à partir du thread UI. Le modèle monothread d'Android comporte deux règles:

  1. Ne bloquez pas le thread UI.
  2. N'accédez pas au kit UI Android en dehors du thread UI.

Threads de calcul

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 devez effectuer des opérations qui ne sont pas instantanées, faites-les dans des threads en arrière-plan ou de nœuds de calcul distincts. N'oubliez pas que vous ne pouvez mettre à jour l'UI à partir d'aucun thread que l'UI, ou thread 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:

L'exemple suivant utilise View.post(Runnable):

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Cette implémentation est thread-safe, car l'opération d'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 nœud de calcul, vous pouvez envisager d'utiliser un Handler dans votre thread de nœud de calcul pour traiter les messages distribués à partir du thread UI. Pour obtenir une explication complète de la planification d'une tâche sur des threads en arrière-plan et d'une communication avec le thread UI, consultez la section Présentation des tâches en arrière-plan.

Méthodes thread-safe

Dans certains cas, les méthodes que vous implémentez sont appelées à partir de plusieurs threads et doivent donc être écrites de manière à être sécurisées.

Cela est principalement vrai pour les méthodes pouvant être appelées à distance, telles que les méthodes dans un service lié. Lorsqu'un appel sur une méthode implémentée dans un IBinder provient du même processus que celui dans lequel IBinder s'exécute, 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 dans 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(), comme une sous-classe qui implémente des méthodes d'appel de procédure à distance (RPC), sont appelées à partir des 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 sécurisées.

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 du thread UI du processus. Étant donné que ces méthodes peuvent être appelées à partir d'un nombre illimité de threads à la fois, elles doivent également être implémentées pour être sécurisées.

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, tous les résultats étant renvoyés à 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 les transmettre du processus local et de l'espace d'adressage au processus et à l'espace d'adressage distants, puis de reconstruire et reconstituer l'appel à ce niveau.

Les valeurs renvoyées sont ensuite transmises dans la direction opposée. Android fournit tout le code permettant d'effectuer ces transactions IPC. Vous pouvez ainsi vous concentrer sur la définition et la mise en œuvre de l'interface de programmation RPC.

Pour effectuer l'IPC, votre application doit s'associer à un service à l'aide de bindService(). Pour en savoir plus, consultez Présentation des services.