Es gibt eine interessante Low-Level-Klasse im Android SDK namens Looper. Jede App hat mindestens eine, denn sie steuert den UI-Thread. Sie können problemlos Android-Apps erstellen, ohne Looper jemals selbst zu verwenden, aber es ist eine interessante Sache, also lassen Sie uns einen Blick unter die Haube werfen.
[caption id="attachment_10765" align="alignnone" width="300"]
Strukturelle Übersicht über die Looper API von Android.[/caption]
Die sichtbaren Bausteine sind Looper, Handler und Nachricht. Hinter den Kulissen gibt es die MessageQueue. Jeder Looper ist an einen einzelnen Thread gebunden, und jeder Thread hat höchstens einen Looper. Der Zugriff auf den Looper erfolgt hauptsächlich über die Klasse Handler.
Handler verfügt über die Methoden, mit denen Sie Nachrichteninstanzen erhalten und sie an die Nachrichtenwarteschlange des zugehörigen Loopers übermitteln. Sobald sich Ihre Nachricht am Anfang der Warteschlange befindet, ist der Handler auch das Objekt, das diese Nachricht verarbeitet. Sie können das Verhalten der Nachrichtenverarbeitung implementieren, indem Sie entweder Handler unterklassifizieren und die Funktion handleMessage(..) Methode überschreiben, oder indem Sie den Handler mit einer Implementierung der Handler.Callback Schnittstelle erstellen.
Nachrichten haben fünf öffentliche Felder. Das int-Feld definiert den Nachrichtentyp. Es ermöglicht einem einzelnen Handler (oder Handler-Callback), mehrere Arten von Nachrichten mit Hilfe eines Switch-Konstrukts zu behandeln. Die beiden
Was ist mit ExecutorService?
Mit Java 1.5 wurde das Paket util.concurrent und mit ihm das Executor-Framework eingeführt. Dieses Framework ist im Android SDK enthalten und der Looper ist vom Zweck her ähnlich wie ein SingleThreadExcecutor, wenn auch deutlich weniger vielseitig: Sie können keine Callables und Futuresverwenden, Sie können Ihre Meinung über die gewählte ExecutorService-Implementierung nicht ändern oder die Länge der Warteschlange einstellen.
Sie könnten also fragen: "Warum das Rad neu erfinden?".
Der Grund dafür ist, dass Looper ein anderes Leistungsprofil hat. Sie erinnern sich, dass der UI-Thread von einem Looper gesteuert wird. Was Sie aus dem oben gezeigten Diagramm nicht ersehen können, ist, dass die
Der goldene Standard für eine Benutzeroberfläche ist eine sechzigmalige Aktualisierung pro Sekunde. Dadurch erhält der Benutzer einen möglichst reibungslosen Bildlauf. Wäre der UI-Thread auf einem Executor-Dienst aufgebaut worden, hätte die Belastung durch den Garbage Collector eine solche Aktualisierungsrate unmöglich gemacht und der Benutzer hätte nicht sehr lange flüssig scrollen können.
Einsatz von Looper
Am häufigsten wird die Looper-API verwendet, um von einem Hintergrundthread auf den Hauptthread zu wechseln. Auf diese Weise werden die Methoden onProgress() und onPostExecute() von AsyncTask implementiert.
[sourcecode language="java"] class HandlerSample implements Handler.Callback { private final Handler mHandler = new Handler( Looper.getMainLooper(), this ); private static final int MSG_TYPE_A = 1; private static final int MSG_TYPE_B = 2; /* Z.B. ein Sensor-Callback oder ein IntentService. / void someMethodThatRunsInTheBackground() { // Hier läuft die Hintergrundverarbeitung ab... // Die Methoden obtainMessage() und sendMessage() sind thread-sicher und können // ohne externe Synchronisierung aufgerufen werden. Nachricht msg = mHandler.obtainMessage( MSG_TYPE_A, resultObj ); mHandler.sendMessage( msg ); } /* Aufgerufen auf dem UI-Thread mittels Looper.getMainLooper(). / @Override public void handleMessage( Message msg ) { switch (msg.what) { case MSG_TYPE_A: // Die Verarbeitung erfolgt hier Pause; Fall MSG_TYPE_B: // Die Verarbeitung erfolgt hier Pause; } } } [/sourcecode]
Wenn Sie übrigens überprüfen wollen, ob der aktuelle Thread der Hauptthread ist, testen Sie einfach ( Looper.myLooper() == Looper.getMainLooper() ). Dies kann eine nützliche Ergänzung sein zu StrictMode.
Ihren eigenen Looper verwenden
Neben dem Hauptlooper ist es durchaus möglich, einen eigenen Looper zu erstellen und die Nachrichtenverarbeitung in einem Hintergrund-Thread durchzuführen. Dies ist von Natur aus ein Low-Level-Konstrukt und nicht alle Anwendungen benötigen es. Peripherie-E/A ist ein guter Kandidat für diese Art der Verarbeitung, z.B. die Bluetooth-Kommunikation mit einem Arduino.
Um einen Looper zu verwenden, müssen Sie Looper.prepare() und Looper.loop() auf dem Thread aufrufen , der die Schleifenbildung durchführen soll. Der Aufruf von loop() kehrt nicht zurück. Von dem Moment an, in dem Sie diese Methode aufrufen, verarbeitet Ihr Thread Nachrichten, bis die Methode quit() auf seinem Looper aufgerufen wird.
[sourcecode language="java"] class BackgroundLooper extends Thread implements Handler.Callback { /* Vorsicht: dieses Feld wird nach start() für einige Zeit leer sein. / public Handler handler; @Override public void run() { Looper.prepare(); handler = new Handler( Looper.myLooper(), this ); Looper.loop(); } @Override public void handleMessage(Message msg) { /... / } } [/sourcecode]
Das SDK enthält HandlerThread, um Ihnen die Arbeit abzunehmen und das Ganze ein wenig zu vereinfachen.
Bemerkungen
- Wenn Sie Handler verwenden, stellen Sie sicher, dass Sie Nachrichten mit einer der send() -Methoden einreihen und nicht mit der dispatchMessage(..) -Methode. Letztere steht in einem alphabetisch sortierten Autovervollständigungs-Popup an erster Stelle, und Sie wären nicht der Erste, der die falsche Methode aufruft ;).
- Wenn Sie einen Handler mit einem Konstruktor erstellen, der kein Looper-Argument akzeptiert, wird er den Looper des aktuellen Threads verwenden und eine Exception auslösen, wenn es keinen gibt. Ich denke, es ist besser, explizit zu sein und immer einen initialisierenden Konstruktor mit entweder Looper.getMainLooper() oder Looper.myLooper() als Argument zu verwenden.
- Die MessageQueue wird nur selten direkt verwendet, aber Sie können sie vom Looper abrufen, um einen IdleHandler hinzuzufügen.
- Seien Sie vorsichtig, wenn Sie an verwalteten Objekten wie Activities und Views in einem Hintergrunddienst festhalten. Genau wie bei AsyncTask können Sie in alle möglichen Schwierigkeiten geraten, wenn Sie auf ein UI-Element verweisen, das nicht mehr gültig ist.
- Wenn Sie Looper.quit() aufrufen, wird der Looper so schnell wie möglich beendet. Er beendet die Verarbeitung der aktuellen Nachricht, aber er verarbeitet keine weiteren Nachrichten in seiner Warteschlange mehr. Wenn Sie möchten, dass der Looper die Verarbeitung der verbleibenden Nachrichten abschließt, legen Sie eine "Giftpille" in die Warteschlange, die Ihren Handler anweist, seinen Looper zu beenden().
Verfasst von

Barend Garvelink
Unsere Ideen
Weitere Blogs
Contact



