Welche Beziehung besteht zwischen Looper, Handler und MessageQueue in Android?

95

Ich habe den offiziellen Android - Dokumentation / Guide geprüft Looper, Handlerund MessageQueue. Aber ich konnte es nicht verstehen. Ich bin neu in Android und war sehr verwirrt mit diesen Konzepten.

Blake
quelle

Antworten:

103

A Looperist eine Nachrichtenbehandlungsschleife: Sie liest und verarbeitet Elemente aus a MessageQueue. Die LooperKlasse wird normalerweise in Verbindung mit a HandlerThread(einer Unterklasse von Thread) verwendet.

A Handlerist eine Dienstprogrammklasse, die die Interaktion mit a erleichtert - Looperhauptsächlich durch das Posten von Nachrichten und RunnableObjekten an den Thread MessageQueue. Wenn a Handlererstellt wird, ist es an eine bestimmte Looper(und zugehörige Thread- und Nachrichtenwarteschlange) gebunden .

In der typischen Verwendung erstellen und starten Sie ein HandlerThreadund erstellen dann ein HandlerObjekt (oder Objekte), über das andere Threads mit der HandlerThreadInstanz interagieren können . Das Handlermuss während der Ausführung auf dem erstellt werden HandlerThread, obwohl es nach dem Erstellen keine Einschränkung dafür gibt, welche Threads die HandlerPlanungsmethoden des Threads verwenden können (post(Runnable) usw.) des .

Der Hauptthread (auch als UI-Thread bezeichnet) in einer Android-Anwendung wird als Handler-Thread eingerichtet, bevor Ihre Anwendungsinstanz erstellt wird.

Abgesehen von den Klassen docs, dann ist es eine schöne Diskussion über all dies hier .

PS Alle oben genannten Klassen sind im Paket enthalten android.os.

Ted Hopp
quelle
@ Ted Hopp - Unterscheidet sich die Nachrichtenwarteschlange von Looper von der Nachrichtenwarteschlange von Thread?
CopsOnRoad
2
@ Jack - Sie sind das gleiche. Die Android API - Dokumentation fürMessageQueue Zustand , dass ein MessageQueueein „ Low-Level - Klasse , um die Liste der Nachrichten halten durch ein abgefertigt werden Looper.
Ted Hopp
95

Es ist allgemein bekannt, dass es illegal ist, UI-Komponenten direkt von anderen Threads als dem Haupt-Thread in Android zu aktualisieren . In diesem Android-Dokument ( Umgang mit teuren Vorgängen im UI-Thread ) werden die folgenden Schritte vorgeschlagen, wenn ein separater Thread gestartet werden muss, um teure Arbeiten auszuführen und die UI nach Abschluss zu aktualisieren. Die Idee ist, ein Handler- Objekt zu erstellen, das dem Hauptthread zugeordnet ist , und zu gegebener Zeit eine ausführbare Datei an dieses zu senden . Dies Runnablewird im Hauptthread aufgerufen . Dieser Mechanismus wird mit Looper- und Handler- Klassen implementiert .

Die LooperKlasse unterhält eine Message , die eine Liste enthält Nachrichten . Ein wichtiges Merkmal des Looper ist , dass es assoziiert mit dem Gewinde in dem die Loopererstellt wird . Diese Zuordnung bleibt für immer erhalten und kann weder gebrochen noch geändert werden. Beachten Sie auch, dass ein Thread nicht mehr als einem zugeordnet werden kann Looper. Um diese Zuordnung zu gewährleisten, Looperwird sie im threadlokalen Speicher gespeichert und kann nicht direkt über ihren Konstruktor erstellt werden. Die einzige Möglichkeit, es zu erstellen, besteht darin, die statische Methode prepare aufzurufen Looper. Die Vorbereitungsmethode untersucht zuerst ThreadLocaldes aktuellen Threads, um sicherzustellen, dass dem Thread noch kein Looper zugeordnet ist. Nach der Prüfung wird eine neue Loopererstellt und gespeichert ThreadLocal. Nachdem Looperwir das vorbereitet haben , können wir die Schleifenmethode aufrufen , um nach neuen Nachrichten zu suchen und zu habenHandler zu suchen und diese zu verarbeiten.

Wie der Name schon sagt, ist die HandlerKlasse hauptsächlich für die Verarbeitung (Hinzufügen, Entfernen, Versenden) von Nachrichten aktueller Threads verantwortlich MessageQueue. Eine HandlerInstanz ist auch an einen Thread gebunden. Die Bindung zwischen Handler und Thread erfolgt über Looperund MessageQueue. A Handlerist immer gebunden ein Looper, und anschließend an den gebundenen zugehörigen Gewinde mit der Looper. Im Gegensatz dazu Looperkönnen mehrere Handler-Instanzen an denselben Thread gebunden werden. Immer wenn wir post oder eine andere Methode auf dem aufrufen Handler, wird dem zugehörigen eine neue Nachricht hinzugefügt MessageQueue. Das Zielfeld der Nachricht wird auf die aktuelle HandlerInstanz gesetzt. Wenn derLooper empfangenen Nachricht dispatchMessage aufruft im Zielfeld der Nachricht aufgerufen, sodass die Nachricht an die zu behandelnde Handler-Instanz zurückgeleitet wird, jedoch im richtigen Thread. Die Beziehungen zwischen Looper, Handlerund MessageQueueist unten gezeigt:

Geben Sie hier die Bildbeschreibung ein

K_Anas
quelle
5
Vielen Dank! aber was ist der Punkt , wenn die Prozedur zuerst schreibt die Nachricht an die Nachrichtenwarteschlange und dann behandelt die Nachricht aus der gleichen Warteschlange? Warum wird die Nachricht nicht direkt verarbeitet?
Blake
4
@Blake b / c Sie posten von einem Thread (Nicht-Looper-Thread), aber behandeln die Nachricht in einem anderen Thread (Looper-Thread)
numan salati
Viel besser als das, was auf developer.android.com dokumentiert wird - aber es wäre schön, Code für das von Ihnen bereitgestellte Diagramm zu sehen.
tfmontague
@numansalati - Kann der Handler keine Nachrichten aus dem Looper-Thread posten?
CopsOnRoad
78

Beginnen wir mit dem Looper. Sie können die Beziehung zwischen Looper, Handler und MessageQueue leichter verstehen, wenn Sie verstehen, was Looper ist. Außerdem können Sie besser verstehen, was Looper im Kontext des GUI-Frameworks ist. Looper ist dafür gemacht, zwei Dinge zu tun.

1) Looper wandelt einen normalen Thread , der bei der run()Rückkehr seiner Methode beendet wird , in einen Thread um , der kontinuierlich ausgeführt wird, bis die Android-App ausgeführt wird , was im GUI-Framework erforderlich ist (technisch gesehen wird er immer noch beendet, wennrun() ist. unten).

2) Looper stellt eine Warteschlange bereit bereit, in der zu erledigende Jobs in die werden, was auch im GUI-Framework erforderlich ist.

Wie Sie vielleicht wissen, erstellt das System beim Start einer Anwendung einen Ausführungsthread für die Anwendung, der als "Hauptthread" bezeichnet wird, und Android-Anwendungen werden normalerweise vollständig auf einem einzelnen Thread ausgeführt, dem "Hauptthread". Aber der Haupt-Thread ist kein geheimer, spezieller Thread . Es ist nur ein normaler Thread, den Sie auch mit new Thread()Code erstellen können. Dies bedeutet, dass er beendet wird, wenn er ausgeführt wirdrun() Methode zurückgegeben wird! Denken Sie an das folgende Beispiel.

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();
    }
}

Wenden wir dieses einfache Prinzip nun auf die Android-App an. Was würde passieren, wenn eine Android-App auf einem normalen Thread ausgeführt wird? Ein Thread namens "main" oder "UI" oder was auch immer startet die Anwendung und zeichnet alle UI. Daher wird den Benutzern der erste Bildschirm angezeigt. So was nun? Der Haupt-Thread endet? Nein, das sollte es nicht. Es sollte warten, bis Benutzer etwas tun, oder? Aber wie können wir dieses Verhalten erreichen? Nun, wir können es mit Object.wait()oder versuchenThread.sleep(). Beispielsweise beendet der Hauptthread seinen anfänglichen Job zum Anzeigen des ersten Bildschirms und schläft. Es erwacht, was bedeutet, unterbrochen, wenn ein neuer Job abgerufen wird. So weit so gut, aber in diesem Moment brauchen wir eine warteschlangenartige Datenstruktur, um mehrere Jobs zu halten. Denken Sie an einen Fall, in dem ein Benutzer den Bildschirm seriell berührt und eine Aufgabe länger dauert. Wir brauchen also eine Datenstruktur, um Jobs zu speichern, die First-In-First-Out ausgeführt werden. Sie können sich auch vorstellen, dass die Implementierung eines Threads, der immer ausgeführt wird und einen Job bei Ankunft verarbeitet, mithilfe von Interrupt nicht einfach ist und zu komplexem und häufig nicht wartbarem Code führt. Wir möchten lieber einen neuen Mechanismus für diesen Zweck schaffen, und darum geht es bei Looper . Das offizielle Dokument der Looper-Klassesagt: "Threads sind standardmäßig keine Nachrichtenschleife zugeordnet", und Looper ist eine Klasse, "die zum Ausführen einer Nachrichtenschleife für einen Thread verwendet wird". Jetzt können Sie verstehen, was es bedeutet.

Wechseln wir zu Handler und MessageQueue. Erstens ist MessageQueue die Warteschlange, die ich oben erwähnt habe. Es befindet sich in einem Looper und das war's. Sie können dies mit dem Quellcode der Looper-Klasse überprüfen . Die Looper-Klasse hat eine Mitgliedsvariable von MessageQueue.

Was ist dann Handler? Wenn es eine Warteschlange gibt, sollte es eine Methode geben, mit der wir eine neue Aufgabe in die Warteschlange einreihen können, oder? Das macht Handler. Wir können eine neue Aufgabe mit verschiedenen post(Runnable r)Methoden in eine Warteschlange (MessageQueue) einreihen . Das ist es. Hier geht es um Looper, Handler und MessageQueue.

Mein letztes Wort ist, also ist Looper im Grunde eine Klasse, die gemacht wird, um ein Problem anzugehen, das im GUI-Framework auftritt. Diese Art von Bedürfnissen kann aber auch in anderen Situationen auftreten. Tatsächlich ist es ein ziemlich berühmtes Muster für Anwendungen mit mehreren Threads, und Sie können mehr darüber in "Concurrent Programming in Java" von Doug Lea erfahren (insbesondere Kapitel 4.1.4 "Worker Threads" wäre hilfreich). Sie können sich auch vorstellen, dass diese Art von Mechanismus im Android-Framework nicht eindeutig ist, aber alle GUI-Frameworks benötigen möglicherweise etwas Ähnliches. Sie finden fast den gleichen Mechanismus im Java Swing Framework.

김준호
quelle
4
Beste Antwort. Erfahren Sie mehr aus dieser detaillierten Erklärung. Ich frage mich, ob es einen Blog-Beitrag gibt, der detaillierter ist.
capt.swag
Können die Nachrichten ohne Verwendung des Handlers zur MessageQueue hinzugefügt werden?
CopsOnRoad
@CopsOnRoad nein, sie können nicht direkt hinzugefügt werden.
Faisal Naseer
Machte meinen Tag ... viel Liebe zu dir :)
Rahul Matte
26

MessageQueue: Es handelt sich um eine untergeordnete Klasse, die die Liste der von a zu versendenden Nachrichten enthält Looper. Nachrichten werden nicht direkt zu a hinzugefügt MessageQueue, sondern über HandlerObjekte, die mit Looper. [ 3 ]

Looper: Es durchläuft eine Schleife, MessageQueuedie die zu versendenden Nachrichten enthält. Die eigentliche Aufgabe der Verwaltung der Warteschlange Handlerübernimmt derjenige, der für die Verarbeitung (Hinzufügen, Entfernen, Versenden) von Nachrichten in der Nachrichtenwarteschlange verantwortlich ist. [ 2 ]

Handler: Es ermöglicht Ihnen , und Prozess zu senden Messageund RunnableObjekte mit einem Thread zugeordnet MessageQueue. Jede Handler-Instanz ist einem einzelnen Thread und der Nachrichtenwarteschlange dieses Threads zugeordnet. [ 4 ]

Wenn Sie eine neue erstellen Handler, wird sie an die Thread- / Nachrichtenwarteschlange des Threads gebunden, der sie erstellt. Ab diesem Zeitpunkt werden Nachrichten und ausführbare Dateien an diese Nachrichtenwarteschlange gesendet und ausgeführt, sobald sie aus der Nachrichtenwarteschlange kommen .

Bitte gehen Sie zum besseren Verständnis das folgende Bild [ 2 ] durch.

Geben Sie hier die Bildbeschreibung ein

AnV
quelle
0

Erweiterung der Antwort um @K_Anas um ein Beispiel, wie angegeben

Es ist allgemein bekannt, dass es illegal ist, UI-Komponenten direkt von anderen Threads als dem Haupt-Thread in Android zu aktualisieren.

Zum Beispiel, wenn Sie versuchen, die Benutzeroberfläche mithilfe von Thread zu aktualisieren.

    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();

Ihre App stürzt mit Ausnahme ab.

android.view.ViewRoot $ CalledFromWrongThreadException: Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine Ansichten berühren.

Mit anderen Worten, Sie müssen verwenden, Handlerwas den Verweis auf die Aufgabe MainLooper ie Main Threadoder UI Threadund als Aufgabe weitergibt 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
quelle