English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Android-Nachrichtenmechanismus
1.Übersicht
Bei der Initialisierung eines Android-Anwendungen wird standardmäßig eine Hauptthread (UI-Thread) vorhanden sein, in dem eine Nachrichtenliste (MessageQueue) verknüpft wird. Alle Operationen werden in Nachrichtenliste verpackt und dann an den Hauptthread übergeben. Um sicherzustellen, dass der Hauptthread nicht beendet wird, werden die Operationen der Nachrichtenliste in einem Endlosschleife platziert, das Programm läuft also in einem Endlosschleifen, bei jedem Schleifendurchlauf wird eine Nachricht aus der internen Nachrichtenliste ausgelesen und die entsprechende Nachrichtenverarbeitungsfunction (handlerMessage) aufgerufen, nach Abschluss einer Nachricht wird der Schleifendurchlauf fortgesetzt, wenn die Nachrichtenliste leer ist, wird der Thread blockiert und wartet. Daher wird er nicht beendet. Wie im folgenden Bild gezeigt:
Was ist der Zusammenhang zwischen Handler, Looper und Message?
Viele Operationen, die in Unterthreads ausgeführt werden, erfordern das Aktualisieren der Benutzeroberfläche, oft durch die Verwendung von Handler, um eine Nachricht in den UI-Thread zu posten, und dann im handlerMessage-Methode des Handlers zu verarbeiten. Jeder Handler ist mit einer Nachrichtenliste (MessageQueue) verbunden, Looper ist verantwortlich für die Erstellung einer MessageQueue, und jeder Looper ist mit einem Thread verbunden (Looper wird durch ThreadLocal verpackt). Standardmäßig gibt es nur eine MessageQueue, nämlich die Nachrichtenliste des Hauptthreads.
Das ist der Grundgedanke des Android-Nachrichtenmechanismus. Wenn Sie mehr Details erfahren möchten, beginnen wir mit dem Quellcode.
2. Quellcodeanalyse
}}1)In ActivityThread Hauptthread starten Nachrichten-Schleife Looper
public final class ActivityThread { public static void main(String[] args) { //Code wird weggelassen //1. Erstellen Sie den Looper für die Nachrichten-Schleife Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); //2. Nachrichten-Schleife ausführen Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
ActivityThread durch Looper.prepareMainLooper() die Nachrichtenliste des Hauptthreads erstellt und schließlich Looper.loop() ausführt, um die Nachrichtenliste zu starten. Handler verknüpft Nachrichtenliste und Thread.
}}2)Handler verknüpft Nachrichtenliste und Thread
public Handler(Callback callback, boolean async) {}} //Code wird weggelassen //Erhalten Sie Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //Erhalten Sie die Nachrichtenqueuelog mQueue = mLooper.mQueue; }
Handler innen über die Methode Looper.getLooper() den Looper-Objekt erhält, und ihn verknüpft, sowie die Nachrichtenliste erhält. Wie funktioniert Looper.getLooper()?
public static @Nullable Looper myLooper() { return sThreadLocal.get(); } public static @NonNull MessageQueue myQueue() { return myLooper().mQueue; } public static void prepare() { prepare(true); } //Setze einen Looper für die aktuelle Thread private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } //Setze den Looper der UI-Thread public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Im Looper-Klasse, die myLooper() Methode, durch sThreadLocal.get() abgerufen, wird im prepareMainLooper() die prepare() Methode aufgerufen, in der ein Looper-Objekt erstellt wird und das Objekt sThreadLocal() gesetzt wird. Auf diese Weise wird die Warteschlange mit dem Thread verknüpft. Durch die Methode sThreadLocal.get() wird sichergestellt, dass verschiedene Threads nicht auf die Nachrichtenliste des anderen Threads zugreifen können.
Warum muss der Handler, der die UI aktualisiert, im Hauptthread erstellt werden?
Weisst du, warum der Handler mit der Nachrichtenliste des Hauptthreads verknüpft werden muss, damit handlerMessage in der UI-Thread ausgeführt wird und der UI-Thread in diesem Moment sicher ist.
}}3Nachrichtenwiederholung, Nachrichtenverarbeitung
Der Aufbau der Nachrichtenwiederholung erfolgt durch die Methode Looper.loop(). Der Quellcode lautet wie folgt:
/** * Führen Sie die Nachrichtenliste in diesem Thread aus. Stellen Sie sicher, dass Sie {@link #quit()} aufrufen, um den Loop zu beenden. * {@link #quit()} zum Beenden des Loops. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("Kein Looper; Looper.prepare() wurde auf diesem Thread nicht aufgerufen."); } //1.Nachrichtenliste abrufen final MessageQueue queue = me.mQueue; //2.Endlosschleife, also Nachrichtenwiederholung for (;;) { //3.Nachrichten abrufen, kann blockieren Message msg = queue.next(); // kann blockieren if (msg == null) { // Keine Nachricht bedeutet, dass die Nachrichtenliste den Dienst beendet. return; } //4.Nachrichten verarbeiten msg.target.dispatchMessage(msg); //Nachrichten回收 msg.recycleUnchecked(); } }
Aus dem obigen Programm können wir erkennen, dass die Substanz der Methode loop() darin besteht, einen Endlosschleife zu erstellen, dann Nachrichten von der Nachrichtenliste nacheinander abzurufen und schließlich Nachrichten zu verarbeiten. Für Looper: Durch Looper.prepare() wird ein Looper-Objekt erstellt (die Nachrichtenliste ist im Looper-Objekt verpackt) und in sThreadLocal gespeichert, und dann wird durch Looper.loop() die Nachrichtenwiederholung durchgeführt, diese beiden Schritte treten in der Regel gemeinsam auf.
public final class Message implements Parcelable { //target behandelt Handler target; //Runnable-artiger callback Runnable callback; //Die nächste Nachricht, die Nachrichtenliste ist in einer kettenförmigen Struktur gespeichert Message next; }
Aus dem Quellcode ist ersichtlich, dass target der Typ Handler ist. Tatsächlich geht es nur einen Kreis herum, um Nachrichten über den Handler an die Nachrichtenliste zu senden, und die Nachrichtenliste verteilt die Nachrichten dann an den Handler zur Verarbeitung. In der Klasse Handle:
//Nachrichtenverarbeitungsfunction, von Unterklasse überschrieben public void handleMessage(Message msg) { } private static void handleCallback(Message message) { message.callback.run(); } //Nachricht verteilen public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
Aus dem obigen Programm lässt sich erkennen, dass dispatchMessage nur eine Verteilungsmethode ist. Wenn der Callback-Typ Runnable leer ist, wird handleMessage zum Verarbeiten der Nachricht aufgerufen, diese Methode ist leer, daher schreiben wir den Code zur Aktualisierung der UI in dieser Funktion; wenn der Callback nicht leer ist, wird handleCallback zum Verarbeiten aufgerufen, diese Methode ruft die run-Methode des Callbacks auf. Dies sind in der Tat zwei Arten der Verteilung durch Handler, z.B. wenn post(Runnable callback) aufgerufen wird, ist der Callback nicht leer. Wenn wir Handler verwenden, um sendMessage auszuführen, wird normalerweise kein Callback gesetzt, daher wird handleMessage ausgeführt.
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public String getMessageName(Message message) { if (message.callback != null) { return message.callback.getClass().getName(); } return "0x" + Integer.toHexString(message.what); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) {}} } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException(" this + "sendMessageAtTime() wurde mit keinem mQueue aufgerufen); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
Aus dem obigen Programm ist zu erkennen, dass bei post(Runnable r) der Runnable in ein Message-Objekt verpackt wird, und der Runnable-Objekt wird als callback des Message-Objekts gesetzt, und schließlich wird das Objekt in die Nachrichtenliste eingefügt. sendMessage wird auf ähnliche Weise implementiert:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
Egal, ob ein Runnable oder eine Message per post aufgerufen wird, es wird die Methode sendMessageDelayed(msg, time) aufgerufen. Der Handler fügt letztendlich die Nachricht der MessageQueue hinzu, und der Looper liest ständig Nachrichten von der MessageQueue und ruft die Methode dispatchMessage des Handlers auf, um Nachrichten zu verteilen. Auf diese Weise werden Nachrichten kontinuierlich erzeugt, hinzugefügt zur MessageQueue und vom Handler verarbeitet, und die Android-Anwendung läuft.
3.Überprüfung
new Thread(){ Handler handler = null; public void run () { handler = new Handler(); ; .start();
Hat das obige Code ein Problem?
Der Looper-Objekt ist ein ThreadLocal, das bedeutet, dass jede Thread ihren eigenen Looper hat, der leer sein kann. Allerdings tritt eine Ausnahme auf, wenn ein Handler-Objekt in einem Unterthread erstellt wird und der Looper leer ist.
public Handler(Callback callback, boolean async) {}} //Code wird weggelassen //Erhalten Sie Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //Erhalten Sie die Nachrichtenqueuelog mQueue = mLooper.mQueue; }
Wenn mLooper leer ist, wird eine Ausnahme ausgelöst. Dies liegt daran, dass der Looper-Objekt nicht erstellt wurde, daher wird sThreadLocal.get() null zurückgeben. Das Grundprinzip des Handlers ist es, eine Verbindung mit dem MessageQueue herzustellen und Nachrichten an den MessageQueue zu senden. Wenn es keinen MessageQueue gibt, ist der Handler nicht erforderlich, und der MessageQueue ist in der Looper versiegelt. Daher kann der Looper nicht leer sein, wenn ein Handler erstellt wird. Die Lösung ist wie folgt:
new Thread(){ Handler handler = null; public void run () { //Erstellen Sie für die aktuelle Thread einen Looper und binden Sie ihn an ThreadLocal Looper.prepare(); handler = new Handler(); //Starten Sie den Nachrichtenzyklus Looper.loop(); ; .start();
Wenn nur der Looper erstellt wird und der Nachrichtenzyklus nicht gestartet wird, wird keine Ausnahme ausgelöst, aber auch durch den Handler mit post() oder sendMessage() wird es nicht effektiv sein. Weil obwohl die Nachricht dem Nachrichtenqueuelog hinzugefügt wird, wurde der Nachrichtenzyklus nicht gestartet, daher wird keine Nachricht aus dem Nachrichtenqueuelog abgerufen und ausgeführt.
Vielen Dank für das Lesen, ich hoffe, es hilft Ihnen weiter, danke für Ihre Unterstützung dieser Website!