Antworten:
A Looper
ist eine Nachrichtenbehandlungsschleife: Sie liest und verarbeitet Elemente aus a MessageQueue
. Die Looper
Klasse wird normalerweise in Verbindung mit a HandlerThread
(einer Unterklasse von Thread
) verwendet.
A Handler
ist eine Dienstprogrammklasse, die die Interaktion mit a erleichtert - Looper
hauptsächlich durch das Posten von Nachrichten und Runnable
Objekten an den Thread MessageQueue
. Wenn a Handler
erstellt wird, ist es an eine bestimmte Looper
(und zugehörige Thread- und Nachrichtenwarteschlange) gebunden .
In der typischen Verwendung erstellen und starten Sie ein HandlerThread
und erstellen dann ein Handler
Objekt (oder Objekte), über das andere Threads mit der HandlerThread
Instanz interagieren können . Das Handler
muss während der Ausführung auf dem erstellt werden HandlerThread
, obwohl es nach dem Erstellen keine Einschränkung dafür gibt, welche Threads die Handler
Planungsmethoden 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
.
MessageQueue
Zustand , dass ein MessageQueue
ein „ Low-Level - Klasse , um die Liste der Nachrichten halten durch ein abgefertigt werden Looper
. “
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 Runnable
wird im Hauptthread aufgerufen . Dieser Mechanismus wird mit Looper- und Handler- Klassen implementiert .
Die Looper
Klasse 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 Looper
erstellt 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, Looper
wird 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 Looper
erstellt und gespeichert ThreadLocal
. Nachdem Looper
wir 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 Handler
Klasse hauptsächlich für die Verarbeitung (Hinzufügen, Entfernen, Versenden) von Nachrichten aktueller Threads verantwortlich MessageQueue
. Eine Handler
Instanz ist auch an einen Thread gebunden. Die Bindung zwischen Handler und Thread erfolgt über Looper
und MessageQueue
. A Handler
ist immer gebunden ein Looper
, und anschließend an den gebundenen zugehörigen Gewinde mit der Looper
. Im Gegensatz dazu Looper
kö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 Handler
Instanz 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
, Handler
und MessageQueue
ist unten gezeigt:
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.
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 Handler
Objekte, die mit Looper
. [ 3 ]
Looper
: Es durchläuft eine Schleife, MessageQueue
die 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 Message
und Runnable
Objekte 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.
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, Handler
was den Verweis auf die Aufgabe MainLooper
ie Main Thread
oder UI Thread
und 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() ;