Processus et Threads

Quickview

  • Chaque application s’exécute dans son propre processus et tous les composants de l’application s’exécutent dans ceprocessus, par défaut
  • Toutes les opérations lentes et bloquantes dans une activité doivent être effectuées dans un nouveau thread, pour éviter de ralentir l’interface utilisateur

Dans ce document

  1. Processus
    1. Cycle de vie du processus
  2. Threads
    1. Threads de travail
    2. Méthodes sûres pour les threads
    3. .safe methods
  3. Interprocess Communication

Lorsqu’un composant d’application démarre et que l’application n’a pas d’autres composants 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 d’une même application s’exécutent dans le même processus et le même thread(appelé thread « principal »). Si un composant de l’application démarre et qu’il existe déjà un processus pour cette application (parce qu’un autre composant de l’application existe), alors le composant est démarré dans ce processus et utilise le même fil d’exécution. Cependant, vous pouvez faire en sorte que les différents composants de votre application s’exécutent dans des processus distincts, et vous pouvez créer des fils d’exécution supplémentaires pour n’importe quel processus.

Ce document traite du fonctionnement des processus et des fils d’exécution dans une application Android.

Processus

Par défaut, tous les composants d’une même application s’exécutent dans le même processus et la plupart des applications ne devraient pas changer cela. Cependant, si vous trouvez que vous devez contrôler le processus auquel un certain composant appartient, vous pouvez le faire dans le fichier manifeste.

L’entrée du manifeste pour chaque type d’élément de composant-<activity>, <service>, <receiver> et <provider>-supporte un attribut android:process qui peut spécifier un processus dans lequel ce composant doit s’exécuter. Vous pouvez définir cet attribut pour que chaque composant s’exécute dans son propre processus ou pour que certains composants partagent un processus alors que d’autres non. Vous pouvez également définirandroid:process pour que les composants de différentes applications s’exécutent dans le mêmeprocessus – à condition que les applications partagent le même ID utilisateur Linux et soient signées avec les mêmes certificats.

L’élément <application> prend également en charge un attribut android:process, pour définir une valeur par défaut qui s’applique à tous les composants.

Android pourrait décider d’arrêter un processus à un moment donné, lorsque la mémoire est faible et requise par d’autres processus qui servent plus immédiatement l’utilisateur. Les composants d’application fonctionnant dans le processus tué sont par conséquent détruits. Un processus est redémarré pour ces composants lorsqu’ils ont à nouveau du travail à faire.

Lorsqu’il décide des processus à tuer, le système Android pèse 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, par rapport à un processus hébergeant des activités visibles. La décision d’arrêter un processus dépend donc de l’état des composants exécutés dans ce processus. Les règles utilisées pour décider des processus à terminer sont discutées ci-dessous.

Cycle de vie des processus

Le système Android essaie de maintenir un processus d’application aussi longtemps que possible, mais il doit éventuellement supprimer les anciens processus pour récupérer de la mémoire pour de nouveaux processus ou des processus plus importants. Pour déterminer les processus à conserver et ceux à tuer, le système place chaque processus dans une « hiérarchie d’importance » basée sur les composants exécutés dans le processus et l’état de ces composants. Les processus ayant la plus faible importance sont éliminés en premier, puis ceux ayant la plus faible importance suivante, et ainsi de suite, selon les besoins pour récupérer les ressources du système.

Il y a cinq niveaux dans la hiérarchie d’importance. La liste suivante présente les différents types de processus par ordre d’importance (le premier processus est le plus important et est éliminé en dernier):

  1. Processus de premier plan

    Processus nécessaire à ce que l’utilisateur est en train de faire. Un processus est considéré comme étant au premier plan si l’une des conditions suivantes est vraie :

    • Il héberge un Activity avec lequel l’utilisateur interagit (la méthode onResume() du Activity a été appelée).
    • Il héberge un Service qui est lié à l’activité avec laquelle l’utilisateur interagit.
    • Il héberge un Service qui s’exécute « au premier plan » – le service a appelé startForeground().
    • Il héberge un Service qui exécute l’un de ses lifecycallbacks (onCreate(), onStart() ou onDestroy()).
    • Il héberge un BroadcastReceiver qui exécute sa méthode onReceive().

    Généralement, seuls quelques processus de premier plan existent à un moment donné. Ils ne sont tués qu’en dernier recours – si la mémoire est si faible qu’ils ne peuvent pas tous continuer à fonctionner. Généralement, à ce moment-là, le périphérique a atteint un état de pagination de la mémoire, donc tuer certains processus d’avant-plan est nécessaire pour que l’interface utilisateur reste réactive.

  2. Processus visible

    Processus qui n’a pas de composants d’avant-plan, mais qui peut quand même affecter ce que l’utilisateur voit à l’écran. Un processus est considéré comme visible si l’une ou l’autre des conditions suivantes est vraie :

    • Il héberge un Activity qui n’est pas au premier plan, mais qui est toujours visible pour l’utilisateur (sa méthode onPause() a été appelée). Cela peut se produire, par exemple, si l’activité de premier plan a démarré une boîte de dialogue, ce qui permet de voir l’activité précédente derrière elle.
    • Il héberge un Service qui est lié à une activité visible (ou de premier plan).

    Un processus visible est considéré comme extrêmement important et ne sera pas tué, sauf si cela est nécessaire pour maintenir tous les processus de premier plan en cours d’exécution.

  3. Processus de service

    Un processus qui exécute un service qui a été démarré avec la méthode startService() et qui n’entre dans aucune des deux catégories supérieures. Bien que les processus de service ne soient pas directement liés à quelque chose que l’utilisateur voit, ils font généralement des choses dont l’utilisateur se soucie (comme jouer de la musique en arrière-plan ou télécharger des données sur le réseau), de sorte que le système les maintient en cours d’exécution à moins qu’il n’y ait pas assez de mémoire pour les conserver avec tous les processus de premier plan et visibles.

  4. Processus d’arrière-plan

    Processus détenant une activité qui n’est pas actuellement visible par l’utilisateur (la méthodeonStop() de l’activité a été appelée). Ces processus n’ont pas d’impact direct sur l’expérience de l’utilisateur, et le système peut les tuer à tout moment pour récupérer de la mémoire pour un processusground,visible, ou de service précédent. En général, de nombreux processus d’arrière-plan sont en cours d’exécution, ils sont donc conservés dans une liste LRU (least recently used) afin de garantir que le processus dont l’activité a été vue le plus récemment par l’utilisateur est le dernier à être tué. Si une activité implémente correctement ses méthodes de cycle de vie et sauvegarde son état actuel, l’arrêt de son processus n’aura pas d’effet visible sur l’expérience de l’utilisateur, car lorsque l’utilisateur navigue à nouveau vers l’activité, celle-ci restaure tout son état visible. Voir le document Activités pour des informations sur la sauvegarde et la restauration de l’état.

  5. Processus vide

    Un processus qui ne contient aucun composant d’application actif. La seule raison de garder ce type de processus en vie est à des fins de mise en cache, pour améliorer le temps de démarrage la prochaine fois qu’un composantnécessite de s’y exécuter. Le système tue souvent ces processus afin d’équilibrer les ressources globales du système entre les caches des processus et les caches sous-jacents du noyau.

Android classe un processus au niveau le plus élevé possible, en fonction de l’importance des composants actuellement actifs dans le processus. Par exemple, si un processus héberge un service et une activité visible, le processus est classé comme un processus visible, et non comme un processus de service.

En outre, le classement d’un processus peut être augmenté parce que d’autres processus en dépendent – un processus qui sert un autre processus ne peut jamais être classé plus bas que le processus qu’il sert. Par exemple, si un fournisseur de contenu dans le processus A sert un client dans le processus B, ou si un service dans le processus A est lié à un composant dans le processus B, le processus A est toujours considéré comme au moins aussi important que le processus B.

Parce qu’un processus qui exécute un service est classé plus haut qu’un processus avec des activités d’arrière-plan,une activité qui initie une opération de longue durée pourrait faire bien de démarrer un service pour cette opération, plutôt que de créer simplement un fil de travail – en particulier si l’opération va probablement dépasser l’activité.Par exemple, une activité qui télécharge une photo sur un site Web devrait lancer un service pour effectuer le téléchargement afin que celui-ci puisse se poursuivre en arrière-plan même si l’utilisateur quitte l’activité.L’utilisation d’un service garantit que l’opération aura au moins la priorité « processus de service », indépendamment de ce qui arrive à l’activité. C’est la même raison pour laquelle les récepteurs de diffusion devraientemployer des services plutôt que de simplement mettre les opérations qui prennent du temps dans un thread.

Threads

Lorsqu’une application est lancée, le système crée un thread d’exécution pour l’application,appelé « main ». Ce thread est très important car il est chargé de distribuer les événements aux widgets appropriés de l’interface utilisateur, y compris les événements de dessin. C’est également le thread dans lequel votre application interagit avec les composants de la boîte à outils Android UI (composants des paquets android.widget et android.view). En tant que tel, le thread principal est aussi parfois appelé le thread UI.

Le système ne crée pas un 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 (comme onKeyDown() pour signaler les actions de l’utilisateur ou une méthode de rappel du cycle de vie) s’exécutent toujours dans le thread UI du processus.

Par exemple, lorsque l’utilisateur touche un bouton sur l’écran, le thread UI de votre application envoie l’événementtouch au widget, qui à son tour définit son état pressé et poste une demande d’invalidation dans la file d’attente des événements. Le thread UI met la demande en file d’attente et notifie au widget qu’il doit se redessiner.

Lorsque votre application effectue un travail intensif en réponse à l’interaction de l’utilisateur, ce modèle de thread unique peut donner de mauvaises performances, à moins que vous n’implémentiez votre application correctement. Plus précisément, si tout se passe dans le thread de l’interface utilisateur, l’exécution d’opérations longues telles que l’accès au réseau ou les requêtes à la base de données bloquera 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 se bloquer. Pire encore, si le thread de l’interface utilisateur est bloqué pendant plus de quelques secondes (environ 5 secondes actuellement), l’utilisateur voit apparaître la fameuse boîte de dialogue « application notresponding » (ANR). L’utilisateur peut alors décider de quitter votre application et de la désinstaller s’il n’est pas satisfait.

En outre, la boîte à outils de l’interface utilisateur d’Andoid n’est pas thread-safe. Ainsi, vous ne devez pas manipulervotre interface utilisateur à partir d’un thread de travailleur-vous devez faire toute manipulation à votre interface utilisateur à partir de l’UIthread. Ainsi, il y a simplement deux règles au modèle de thread unique d’Android :

  1. Ne pas bloquer le thread UI
  2. Ne pas accéder à la boîte à outils Android UI depuis l’extérieur du thread UI

Threads de travail

En raison du modèle de thread unique décrit ci-dessus, il est vital pour la réactivité de l’interface utilisateur de votre application que vous ne bloquiez pas le thread UI. Si vous avez des opérations à effectuer qui ne sont pas instantanées, vous devez vous assurer de les faire dans des threads séparés (threads « background » ou « worker »).

Par exemple, voici du code pour un click listener qui télécharge une image à partir d’un separatethread et l’affiche dans un ImageView:

public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start();}

Au début, cela semble fonctionner correctement, car il crée un nouveau thread pour gérer l’opération réseau. Cependant, il viole la deuxième règle du modèle monofilaire : ne pas accéder à la boîte à outilsAndroid UI en dehors du thread UI – cet échantillon modifie le ImageView à partir du thread worker au lieu du thread UI. Cela peut entraîner un comportement indéfini et inattendu, qui peut être difficile et long à dépister.

Pour résoudre ce problème, Android offre plusieurs façons d’accéder au thread UI depuis d’autresthreads. Voici une liste de méthodes qui peuvent aider:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

Par exemple, vous pouvez corriger le code ci-dessus en utilisant la méthode View.post(Runnable) :

public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start();}

Maintenant cette implémentation est thread-safe : l’opération réseau est faite à partir d’un thread séparé tandis que le ImageView est manipulé à partir du thread UI.

Cependant, à mesure que la complexité de l’opération augmente, ce type de code peut devenir compliqué et difficile à maintenir. Pour gérer des interactions plus complexes avec un fil de travail, vous pourriez envisager d’utiliser un Handler dans votre fil de travail, pour traiter les messages délivrés par le fil de l’interface utilisateur. Cependant, la meilleure solution est peut-être d’étendre la classe AsyncTask,qui simplifie l’exécution des tâches du fil de travail qui doivent interagir avec l’interface utilisateur.

Utiliser AsyncTask

AsyncTask vous permet d’effectuer un travail asynchrone sur votre interface utilisateur. Elle effectue les opérations de blocage dans un thread de travailleur et publie ensuite les résultats sur le thread de l’interface utilisateur, sans vous obliger à gérer vous-même les threads et/ou les gestionnaires.

Pour l’utiliser, vous devez sous-classer AsyncTask et implémenter la méthode de rappel doInBackground(), qui s’exécute dans un pool de threads d’arrière-plan. Pour mettre à jour votre interface utilisateur, vous devez implémenter onPostExecute(), qui fournit le résultat de doInBackground() et s’exécute dans le thread de l’interface utilisateur, de sorte que vous pouvez mettre à jour votre interface utilisateur en toute sécurité. Vous pouvez ensuite exécuter la tâche en appelant execute()depuis le thread UI.

Par exemple, vous pouvez implémenter l’exemple précédent en utilisant AsyncTask de cette façon:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); }}

Maintenant l’UI est sûre et le code est plus simple, parce qu’il sépare le travail dans la partie qui doit être faite sur un thread worker et la partie qui doit être faite sur le thread UI.

Vous devriez lire la référence AsyncTask pour une compréhension complète de la façon d’utiliser cette classe, mais voici un aperçu rapide de son fonctionnement :

  • Vous pouvez spécifier le type des paramètres, les valeurs de progression, et la valeur finale de la tâche, en utilisant des génériques
  • La méthode doInBackground() s’exécute automatiquement sur un thread worker
  • onPreExecute(), onPostExecute(), et onProgressUpdate() sont toutes invoquées sur le thread UI
  • La valeur renvoyée par doInBackground() est envoyée àonPostExecute()
  • Vous pouvez appeler publishProgress() à tout moment dans doInBackground() pour exécuter onProgressUpdate() sur le thread UI
  • Vous pouvez annuler la tâche à tout moment, depuis n’importe quel thread

Attention : Un autre problème que vous pouvez rencontrer lors de l’utilisation d’un workerthread est le redémarrage inattendu de votre activité en raison d’un changement de configuration d’exécution(comme lorsque l’utilisateur change l’orientation de l’écran), ce qui peut détruire votre worker thread. Pour voir comment vous pouvez faire persister votre tâche pendant l’un de ces redémarrages et comment annuler correctement la tâche lorsque l’activité est détruite, consultez le code source de l’application d’exemple Shelves.

Méthodes thread-safe

Dans certaines situations, les méthodes que vous implémentez pourraient être appelées depuis plus d’un thread, et doivent donc être écrites pour être thread-safe.

Cela est principalement vrai pour les méthodes qui peuvent être appelées à distance – comme 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 s’exécute, la méthode est exécutée dans le thread de l’appelant.Cependant, lorsque l’appel provient d’un autre processus, la méthode est exécutée dans un thread choisi dans un pool de threads que le système maintient dans le même processus que le 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 serait appelée depuis le thread UI du processus du service, les méthodes implémentées dans l’objet que onBind() renvoie (par exemple, une sous-classe qui implémente des méthodes RPC) seraient appelées depuis les threads du pool. Parce qu’un service peut avoir plus d’un client, plus d’un thread du pool peut 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 demandes de données qui proviennent d’autres processus.Bien que les classes ContentResolver et ContentProvider cachent les détails de la gestion de la communication interprocessus, les méthodes ContentProvider qui répondent à ces demandes – 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 pas à partir du UIthread du processus. Comme 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 interprocessus

Android offre un mécanisme de communication interprocessus (IPC) utilisant des appels de procédure à distance (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 tout résultat renvoyé à l’appelant. Cela implique de décomposer un appel de méthode et ses données à un niveau compréhensible par le système d’exploitation, de les transmettre du processus et de l’espace d’adressage locaux au processus et à l’espace d’adressage distants, puis de réassembler et d’exécuter à nouveau l’appel. Les valeurs de retour sont ensuite transmises dans le sens inverse. Android fournit tout le code pour effectuer ces transactions IPC, de sorte que vous pouvez vous concentrer sur la définition et la mise en œuvre de l’interface de programmation RPC.

Pour effectuer des IPC, votre application doit se lier à un service, en utilisant bindService(). Pour plus d’informations, consultez le guide du développeur Services.

Laisser un commentaire