Quelle est la relation entre Looper, Handler et MessageQueue sous Android?

95

J'ai vérifié la documentation / guide officiel Android pour Looper, Handleret MessageQueue. Mais je n'ai pas pu l'obtenir. Je suis nouveau sur Android et j'ai été très confus avec ces concepts.

Blake
la source

Réponses:

103

A Looperest une boucle de gestion de messages: il lit et traite les éléments d'un fichier MessageQueue. La Looperclasse est généralement utilisée en conjonction avec une HandlerThread(une sous-classe de Thread).

A Handlerest une classe utilitaire qui facilite l'interaction avec un - Looperprincipalement en publiant des messages et des Runnableobjets dans le thread MessageQueue. Lorsqu'une Handlerest créée, elle est liée à un Looperthread spécifique (et associé à une file de discussion et de messages).

Dans une utilisation classique, vous créez et démarrez un HandlerThread, puis créez un Handlerobjet (ou des objets) par lesquels d'autres threads peuvent interagir avec l' HandlerThreadinstance. Le Handlerdoit être créé lors de l'exécution sur le HandlerThread, bien qu'une fois créé, il n'y ait aucune restriction sur les threads pouvant utiliser les Handlerméthodes de planification de s ( post(Runnable), etc.)

Le thread principal (aka thread UI) dans une application Android est configuré en tant que thread de gestionnaire avant la création de votre instance d'application.

En plus des documents de classe, il y a une belle discussion sur tout cela ici .

PS Toutes les classes mentionnées ci-dessus sont dans le package android.os.

Ted Hopp
la source
@Ted Hopp - La file d'attente de messages de Looper est-elle différente de la file d'attente de messages de Thread?
CopsOnRoad
2
@Jack - C'est la même chose. La documentation de l'APIMessageQueue Android pour indiquer que a MessageQueueest une " classe de bas niveau contenant la liste des messages à envoyer par a Looper. "
Ted Hopp
95

Il est bien connu qu'il est illégal de mettre à jour les composants de l'interface utilisateur directement à partir de threads autres que le thread principal dans Android. Ce document Android ( Gestion des opérations coûteuses dans le thread d'interface utilisateur ) suggère les étapes à suivre si nous devons démarrer un thread séparé pour effectuer un travail coûteux et mettre à jour l'interface utilisateur une fois terminé. L'idée est de créer un objet Handler associé au thread principal et d'y publier un Runnable au moment opportun. Cela Runnablesera appelé sur le thread principal . Ce mécanisme est implémenté avec les classes Looper et Handler .

La Looperclasse gère un MessageQueue , qui contient une liste de messages . Un caractère important de Looper est qu'il est associé au thread dans lequel le Looperest créé . Cette association est conservée pour toujours et ne peut être ni rompue ni modifiée. Notez également qu'un fil ne peut pas être associé à plus d' un Looper. Afin de garantir cette association, Looperest stocké dans le stockage local du thread, et il ne peut pas être créé directement via son constructeur. La seule façon de le créer est d'appeler la méthode prepare static on Looper. préparer la méthode examine d'abord ThreadLocaldu thread actuel pour vous assurer qu'il n'y a pas déjà un Looper associé au thread. Après l'examen, un nouveau Looperest créé et enregistré dans ThreadLocal. Après avoir préparé le Looper, nous pouvons appeler la méthode de boucle dessus pour vérifier les nouveaux messages et les Handlertraiter.

Comme son nom l'indique, la Handlerclasse est principalement chargée de gérer (ajouter, supprimer, distribuer) les messages des threads actuels MessageQueue. Une Handlerinstance est également liée à un thread. La liaison entre Handler et Thread est réalisée via Looperet MessageQueue. A Handlerest toujours lié à a Looper, puis lié au thread associé au Looper. Contrairement à Looper, plusieurs instances de Handler peuvent être liées au même thread. Chaque fois que nous appelons post ou toute autre méthode similaire sur le Handler, un nouveau message est ajouté au fichier associé MessageQueue. Le champ cible du message est défini sur l' Handlerinstance actuelle . Quand leLoopera reçu ce message, il invoque dispatchMessage sur le champ cible du message, afin que le message soit renvoyé vers l'instance Handler à traiter, mais sur le thread correct. Les relations entre Looper, Handleret MessageQueueest présenté ci - dessous:

entrez la description de l'image ici

K_Anas
la source
5
Merci! mais ce qui est le point si le gestionnaire premier poster le message à la file d' attente de messages et gérer le message de la même file d' attente? pourquoi ne gère-t-il pas simplement le message directement?
Blake
4
@Blake b / c vous publiez à partir d'un fil (fil non boucleur) mais gérant le message dans un autre fil (fil boucleur)
numan salati
Bien mieux que ce qui est documenté sur developer.android.com - mais ce serait bien de voir le code du diagramme que vous avez fourni.
tfmontague
@numansalati - Le gestionnaire ne peut-il pas envoyer de messages à partir du thread du looper?
CopsOnRoad
78

Commençons par le Looper. Vous pouvez comprendre plus facilement la relation entre Looper, Handler et MessageQueue lorsque vous comprenez ce qu'est Looper. Vous pouvez également mieux comprendre ce qu'est Looper dans le contexte du framework GUI. Looper est fait pour faire 2 choses.

1) Looper transforme un thread normal , qui se termine lorsque sa run()méthode retourne, en quelque chose qui s'exécute en continu jusqu'à ce que l'application Android soit en cours d'exécution , ce qui est nécessaire dans le cadre de l'interface graphique (techniquement, il se termine toujours lorsque la run()méthode revient. Mais laissez-moi clarifier ce que je veux dire, au dessous de).

2) Looper fournit une file d'attente dans laquelle les travaux à effectuer sont mis en file d'attente, ce qui est également nécessaire dans le cadre de l'interface graphique.

Comme vous le savez peut-être, lorsqu'une application est lancée, le système crée un thread d'exécution pour l'application, appelé «main», et les applications Android fonctionnent normalement entièrement sur un seul thread par défaut le «thread principal». Mais le fil principal n'est pas un fil spécial et secret . C'est juste un thread normal que vous pouvez également créer avec du new Thread()code, ce qui signifie qu'il se termine lorsque sa run()méthode revient! Pensez à l'exemple ci-dessous.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Maintenant, appliquons ce principe simple à l'application Android. Que se passerait-il si une application Android était exécutée sur un thread normal? Un thread appelé "main" ou "UI" ou quoi que ce soit démarre l'application, et dessine toute l'interface utilisateur. Ainsi, le premier écran est affiché aux utilisateurs. Et maintenant? Le thread principal se termine? Non, ça ne devrait pas. Il devrait attendre que les utilisateurs fassent quelque chose, non? Mais comment parvenir à ce comportement? Eh bien, nous pouvons essayer avec Object.wait()ouThread.sleep(). Par exemple, le thread principal termine son travail initial pour afficher le premier écran et se met en veille. Il se réveille, ce qui signifie interrompu, lorsqu'un nouveau travail à faire est récupéré. Jusqu'ici tout va bien, mais pour le moment, nous avons besoin d'une structure de données en forme de file d'attente pour contenir plusieurs tâches. Pensez à un cas où un utilisateur touche l'écran en série et qu'une tâche prend plus de temps à se terminer. Nous avons donc besoin d'une structure de données pour conserver les tâches à effectuer de la manière premier entré, premier sorti. De plus, vous pouvez imaginer que l'implémentation d'un thread toujours en cours d'exécution et de traitement à l'arrivée à l'aide d'interruption n'est pas facile et conduit à un code complexe et souvent impossible à maintenir. Nous préférerions créer un nouveau mécanisme à cette fin, et c'est ce qu'est Looper . Le document officiel de la classe Looperdit, "Les threads par défaut n'ont pas de boucle de message associée", et Looper est une classe "utilisée pour exécuter une boucle de message pour un thread". Vous pouvez maintenant comprendre ce que cela signifie.

Passons à Handler et MessageQueue. Tout d'abord, MessageQueue est la file d'attente que j'ai mentionnée ci-dessus. Il réside dans un Looper, et c'est tout. Vous pouvez le vérifier avec le code source de la classe Looper . La classe Looper a une variable membre de MessageQueue.

Alors, qu'est-ce que Handler? S'il y a une file d'attente, alors il devrait y avoir une méthode qui devrait nous permettre de mettre une nouvelle tâche en file d'attente dans la file d'attente, non? C'est ce que fait Handler. Nous pouvons mettre en file d'attente une nouvelle tâche dans une file d'attente (MessageQueue) en utilisant diverses post(Runnable r)méthodes. C'est tout. Tout cela concerne Looper, Handler et MessageQueue.

Mon dernier mot est, donc fondamentalement, Looper est une classe conçue pour résoudre un problème qui se produit dans le cadre de l'interface graphique. Mais ce type de besoins peut également se produire dans d'autres situations. En fait, c'est un modèle assez célèbre pour les applications multi threads, et vous pouvez en apprendre plus à ce sujet dans "Programmation simultanée en Java" par Doug Lea (en particulier, le chapitre 4.1.4 "Worker Threads" serait utile). En outre, vous pouvez imaginer que ce type de mécanisme n'est pas unique dans le cadre Android, mais tous les cadres GUI peuvent nécessiter un peu similaire à celui-ci. Vous pouvez trouver presque le même mécanisme dans le framework Java Swing.

김준호
la source
4
Meilleure réponse. J'ai appris plus de cette explication détaillée. Je me demande s'il y a un article de blog qui va plus en détail.
capt.swag
Les messages peuvent-ils être ajoutés à MessageQueue sans utiliser Handler?
CopsOnRoad
@CopsOnRoad non, ils ne peuvent pas être ajoutés directement.
Faisal Naseer le
Fait ma journée ... beaucoup d'amour pour toi :)
Rahul Matte
26

MessageQueue: C'est une classe de bas niveau contenant la liste des messages à envoyer par un Looper. Les messages ne sont pas ajoutés directement à un MessageQueue, mais plutôt via des Handlerobjets associés à Looper. [ 3 ]

Looper: Il boucle sur un MessageQueuequi contient les messages à envoyer. La tâche réelle de gestion de la file d'attente est effectuée par le Handlerqui est chargé de gérer (ajouter, supprimer, distribuer) les messages dans la file d'attente de messages. [ 2 ]

Handler: Il vous permet d'envoyer et de processus Messageet des Runnableobjets associés à un fil de MessageQueue. Chaque instance de gestionnaire est associée à un seul thread et à la file d'attente de messages de ce thread. [ 4 ]

Lorsque vous créez un nouveau Handler, il est lié au thread / file d'attente de messages du thread qui le crée - à partir de ce moment, il remettra les messages et les exécutables à cette file d'attente de messages et les exécutera lorsqu'ils sortent de la file d'attente de messages .

Veuillez consulter l'image ci-dessous [ 2 ] pour une meilleure compréhension.

entrez la description de l'image ici

AnV
la source
0

Étendre la réponse, par @K_Anas, avec un exemple, comme indiqué

Il est largement connu qu'il est illégal de mettre à jour les composants de l'interface utilisateur directement à partir de threads autres que le thread principal dans Android.

par exemple, si vous essayez de mettre à jour l'interface utilisateur à l'aide de Thread.

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

votre application plantera avec exception.

android.view.ViewRoot $ CalledFromWrongThreadException: seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues.

en d'autres termes, vous devez utiliser Handlerqui garde la référence à la tâche MainLooper ie Main Threadou UI Threadet passez en tant que Runnable.

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
Faisal Naseer
la source