Advertisement
  1. Code
  2. Android SDK

Reaktive Programmieroperatoren in RxJava 2

by
Read Time:14 minsLanguages:

German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)

Wenn Ihre Android-App diese Fünf-Sterne-Bewertungen bei Google Play sammeln wird, muss sie Multitasking-fähig sein.

Als absolutes Minimum erwarten mobile Nutzer von heute, dass sie weiterhin mit Ihrer App interagieren können, während sie im Hintergrund arbeitet. Das mag einfach klingen, aber Android ist standardmäßig Single-Threading. Wenn Sie also die Erwartungen Ihres Publikums erfüllen möchten, müssen Sie früher oder später einige zusätzliche Threads erstellen.

Im vorherigen Artikel dieser Serie haben wir eine Einführung in RxJava erhalten, eine reaktive Bibliothek für die JVM, mit der Sie Android-Anwendungen erstellen können, die auf Daten und Ereignisse reagieren, sobald sie auftreten. Sie können diese Bibliothek aber auch verwenden, um gleichzeitig auf Daten und Ereignisse zu reagieren.

In diesem Beitrag zeige ich Ihnen, wie Sie mit den Operatoren von RxJava die Parallelität auf Android endlich zu einem schmerzfreien Erlebnis machen können. Am Ende dieses Artikels werden Sie wissen, wie Sie RxJava-Operatoren verwenden, um zusätzliche Threads zu erstellen, die Arbeit anzugeben, die in diesen Threads ausgeführt werden soll, und dann die Ergebnisse zurück in den wichtigen Haupt-UI-Thread von Android posten – alles mit nur einem paar Zeilen Code.

Und da keine Technologie perfekt ist, erzähle ich Ihnen auch von einem großen potenziellen Fallstrick beim Hinzufügen der RxJava-Bibliothek zu Ihren Projekten – bevor Sie Ihnen zeigen, wie Sie Operatoren verwenden, um sicherzustellen, dass dieses Problem in Ihren eigenen Android-Projekten nie auftritt.

Operatoren vorstellen

RxJava verfügt über eine enorme Sammlung von Operatoren, die Ihnen hauptsächlich dabei helfen sollen, die von Ihren Observable ausgegebenen Daten zu modifizieren, zu filtern, zusammenzuführen und zu transformieren. Sie finden die vollständige Liste der RxJava-Operatoren in den offiziellen Dokumenten, und obwohl niemand erwartet, dass Sie sich jeden einzelnen Operator merken, lohnt es sich, einige Zeit damit zu verbringen, diese Liste durchzulesen, nur damit Sie eine ungefähre Vorstellung von den verschiedenen Daten haben Transformationen, die Sie durchführen können.

Die Liste der Operatoren von RxJava ist bereits ziemlich vollständig, aber wenn Sie nicht den perfekten Operator für die gewünschte Datentransformation finden können, können Sie immer mehrere Operatoren miteinander verketten. Das Anwenden eines Operators auf ein Observable gibt normalerweise ein anderes Observable zurück, sodass Sie einfach so lange Operatoren anwenden können, bis Sie die gewünschten Ergebnisse erhalten.

Es gibt viel zu viele RxJava-Operatoren, um in einem einzigen Artikel behandelt zu werden, und die offiziellen RxJava-Dokumente leisten bereits gute Arbeit, um alle Operatoren vorzustellen, die Sie für Datentransformationen verwenden können Das größte Potenzial, dir das Leben als Android-Entwickler zu erleichtern: subscribeOn() und observeOn().

Multithreading mit RxJava-Operatoren

Wenn Ihre App die bestmögliche Benutzererfahrung bieten soll, muss sie in der Lage sein, intensive oder lang andauernde Aufgaben auszuführen und mehrere Aufgaben gleichzeitig auszuführen, ohne den wichtigen Haupt-UI-Thread von Android zu blockieren.

Stellen Sie sich beispielsweise vor, Ihre App muss einige Informationen aus zwei verschiedenen Datenbanken abrufen. Wenn Sie diese beiden Aufgaben nacheinander im Hauptthread von Android ausführen, wird dies nicht nur viel Zeit in Anspruch nehmen, sondern die Benutzeroberfläche reagiert auch nicht mehr, bis Ihre App alle Informationen aus beiden Datenbanken abgerufen hat. Nicht gerade eine großartige Benutzererfahrung!

Eine weitaus bessere Lösung besteht darin, zwei zusätzliche Threads zu erstellen, in denen Sie diese beiden Aufgaben gleichzeitig ausführen können, ohne dass sie den Haupt-UI-Thread blockieren. Dieser Ansatz bedeutet, dass die Arbeit doppelt so schnell erledigt wird und der Benutzer weiterhin mit der Benutzeroberfläche Ihrer App interagieren kann. Möglicherweise bemerken Ihre Benutzer nicht einmal, dass Ihre App im Hintergrund intensive und lang andauernde Arbeit verrichtet – alle Datenbankinformationen werden einfach wie von Zauberhand in der Benutzeroberfläche Ihrer Anwendung angezeigt!

Android bietet standardmäßig einige Tools, mit denen Sie zusätzliche Threads erstellen können, einschließlich Service und IntentService, aber diese Lösungen sind schwierig zu implementieren und können schnell zu komplexem, ausführlichen Code führen. Wenn Sie Multithreading nicht richtig implementieren, finden Sie sich außerdem möglicherweise mit einer Anwendung wieder, die Speicher verliert und alle Arten von Fehlern auslöst.

Um Multithreading auf Android noch mehr Kopfschmerzen zu bereiten, ist der Haupt-UI-Thread von Android der einzige Thread, der die Benutzeroberfläche Ihrer App aktualisieren kann. Wenn Sie die Benutzeroberfläche Ihrer App mit dem Ergebnis der Arbeit aktualisieren möchten, die in einem anderen Thread ausgeführt wurde, müssen Sie normalerweise einen Handler im Haupt-UI-Thread erstellen und dann diesen Handler verwenden, um Daten von Ihrem Hintergrundthread in den Haupt-Bedroung. Dies bedeutet mehr Code, mehr Komplexität und mehr Möglichkeiten für Fehler, die sich in Ihr Projekt einschleichen.

Aber RxJava bietet zwei Operatoren, die Ihnen helfen können, einen Großteil dieser Komplexität und Fehlerpotenziale zu vermeiden.

Beachten Sie, dass Sie diese Operatoren in Verbindung mit Schedulers verwenden, bei denen es sich im Wesentlichen um Komponenten handelt, mit denen Sie Threads angeben können. Stellen Sie sich vorerst nur Scheduler als Synonym für das Wort Thread vor.

  • subscribeOn(Scheduler): Standardmäßig gibt ein Observable seine Daten in dem Thread aus, in dem das Abonnement deklariert wurde, d. h. wo Sie die .subscribe-Methode aufgerufen haben. In Android ist dies im Allgemeinen der Haupt-UI-Thread. Sie können den Operator subscribeOn() verwenden, um einen anderen Scheduler zu definieren, in dem das Observable seine Daten ausführen und ausgeben soll.
  • observeOn(Scheduler): Sie können diesen Operator verwenden, um die Emissionen Ihres Observable an einen anderen Scheduler umzuleiten, um effektiv den Thread zu ändern, an den die Benachrichtigungen des Observable gesendet werden, und damit auch den Thread, in dem seine Daten konsumiert werden.

RxJava wird mit einer Reihe von Schedulern geliefert, mit denen Sie verschiedene Threads erstellen können, darunter:

  • Schedulers.io(): Entwickelt, um für IO-bezogene Aufgaben verwendet zu werden.
  • Schedulers.computation(): Entwickelt für Rechenaufgaben. Standardmäßig ist die Anzahl der Threads im Berechnungsplaner auf die Anzahl der auf Ihrem Gerät verfügbaren CPUs beschränkt.
  • Schedulers.newThread(): Erstellt einen neuen Thread.

Jetzt haben Sie einen Überblick über alle beweglichen Teile, sehen wir uns einige Beispiele für die Verwendung von subscribeOn() und observeOn() an und sehen wir uns einige Scheduler in Aktion an.

subscribeOn()

In Android verwenden Sie normalerweise subscribeOn() und einen begleitenden Scheduler, um den Thread zu ändern, in dem langwierige oder intensive Arbeit ausgeführt wird, sodass keine Gefahr besteht, den Haupt-UI-Thread zu blockieren. Sie könnten sich beispielsweise entscheiden, eine große Datenmenge in den io()-Scheduler zu importieren oder einige Berechnungen mit dem computation()-Scheduler durchzuführen.

Im folgenden Code erstellen wir einen neuen Thread, in dem das Observable seine Operationen ausführt und die Werte 1, 2 und 3 ausgibt.

Obwohl dies alles ist, was Sie brauchen, um einen Thread zu erstellen und mit der Ausgabe von Daten für diesen Thread zu beginnen, möchten Sie vielleicht eine Bestätigung, dass diese Observable wirklich in einem neuen Thread arbeitet. Eine Methode besteht darin, den Namen des Threads, den Ihre Anwendung derzeit verwendet, im Logcat-Monitor von Android Studio zu drucken.

Praktischerweise haben wir im vorherigen Beitrag Get Started With RxJava eine Anwendung erstellt, die in verschiedenen Phasen des Lebenszyklus des Observable Nachrichten an Logcat Monitor sendet, sodass wir einen Großteil dieses Codes wiederverwenden können.

Öffnen Sie das Projekt, das Sie in diesem Beitrag erstellt haben, und passen Sie Ihren Code so an, dass er das obige Observable als Quell-Observable verwendet. Fügen Sie dann den subscribeOn()-Operator hinzu und geben Sie an, dass die an Logcat gesendeten Nachrichten den Namen des aktuellen Threads enthalten sollen.

Ihr fertiges Projekt sollte ungefähr so aussehen:

Stellen Sie sicher, dass der Logcat Monitor von Android Studio geöffnet ist (indem Sie die Registerkarte Android Monitor, gefolgt von Logcat auswählen) und führen Sie Ihr Projekt dann entweder auf einem physischen Android-Gerät oder einem AVD aus. Sie sollten die folgende Ausgabe im Logcat-Monitor sehen:

Check the thread where your application is currently running in Android Studios Logcat MonitorCheck the thread where your application is currently running in Android Studios Logcat MonitorCheck the thread where your application is currently running in Android Studios Logcat Monitor

Hier sehen Sie, dass .subscribe im Haupt-UI-Thread aufgerufen wird, das Observable jedoch in einem völlig anderen Thread arbeitet.

Der subscribeOn()-Operator hat den gleichen Effekt, egal wo Sie ihn in der beobachtbaren Kette platzieren; Sie können jedoch nicht mehrere subscribeOn()-Operatoren in derselben Kette verwenden. Wenn Sie mehr als ein subscribeOn() einschließen, verwendet Ihre Kette nur das subscribeOn(), das dem Quell-Observable am nächsten ist.

observeOn()

Im Gegensatz zu subscribeOn(), bei dem Sie observeOn() in Ihrer Kette platzieren, ist das wichtig, da dieser Operator nur den Thread ändert, der von den Observables verwendet wird, die nachgelagert erscheinen.

Wenn Sie beispielsweise Folgendes in Ihre Kette eingefügt haben, verwendet jedes Observable, das ab diesem Zeitpunkt in der Kette auftaucht, den neuen Thread.

Diese Kette läuft auf dem neuen Thread weiter, bis sie auf einen anderen observeOn()-Operator stößt, woraufhin sie zu dem von diesem Operator angegebenen Thread wechselt. Sie können den Thread steuern, an den bestimmte Observables ihre Benachrichtigungen senden, indem Sie mehrere observeOn()-Operatoren in Ihre Kette einfügen.

Bei der Entwicklung von Android-Apps verwenden Sie im Allgemeinen observeOn(), um das Ergebnis der Arbeit an Hintergrundthreads an den Haupt-UI-Thread von Android zu senden. Der einfachste Weg, Emissionen an den Haupt-UI-Thread von Android umzuleiten, besteht darin, den AndroidSchedulers.mainThread Scheduler zu verwenden, der als Teil der RxAndroid-Bibliothek anstelle der RxJava-Bibliothek enthalten ist.

Die RxAndroid-Bibliothek enthält Android-spezifische Bindungen für RxJava 2, was sie zu einer wertvollen zusätzlichen Ressource für Android-Entwickler macht (auf die wir im nächsten Beitrag dieser Serie noch viel genauer eingehen werden).

Um RxAndroid zu Ihrem Projekt hinzuzufügen, öffnen Sie Ihre Datei build.gradle auf Modulebene und fügen Sie die neueste Version der Bibliothek zum Abschnitt Abhängigkeiten hinzu. Zum Zeitpunkt des Schreibens war die neueste Version von RxAndroid 2.0.1, daher füge ich Folgendes hinzu:

Nachdem Sie diese Bibliothek zu Ihrem Projekt hinzugefügt haben, können Sie angeben, dass die Ergebnisse eines Observables mit einer einzigen Codezeile an den Haupt-UI-Thread Ihrer App gesendet werden sollen:

Wenn man bedenkt, dass die Kommunikation mit dem Haupt-UI-Thread Ihrer App eine ganze Seite der offiziellen Android-Dokumente einnimmt, ist dies eine enorme Verbesserung, die Ihnen beim Erstellen von Multithread-Android-Anwendungen möglicherweise viel Zeit sparen könnte.

Der größte Nachteil von RxJava

Während RxJava Android-Entwicklern viel zu bieten hat, ist keine Technologie perfekt, und RxJava hat eine große Falle, die das Potenzial hat, Ihre App zum Absturz zu bringen.

Standardmäßig arbeitet RxJava mit einem Push-basierten Workflow: Daten werden vorgelagert von einem Observable erzeugt und dann nachgelagert an den zugewiesenen Observer gepusht. Das Hauptproblem bei einem Push-basierten Workflow besteht darin, wie leicht es für den Produzenten (in diesem Fall das Observable) ist, Artikel zu schnell auszugeben, damit der Verbraucher (Observer) sie verarbeiten kann.

Ein gesprächiges Observable und ein langsamer Observer können schnell zu einem Rückstand an nicht verbrauchten Elementen führen, der Systemressourcen verschlingt und sogar zu einer OutOfMemoryException führen kann. Dieses Problem wird als Gegendruck bezeichnet.

Wenn Sie vermuten, dass in Ihrer App ein Gegendruck auftritt, gibt es einige mögliche Lösungen, einschließlich der Verwendung eines Bedieners, um die Anzahl der produzierten Artikel zu reduzieren.

Erstellen von Sampling-Perioden mit sample() und throttlefirst()

Wenn ein Observable eine große Anzahl von Elementen aussendet, muss der zugewiesene Observer möglicherweise nicht jedes einzelne dieser Elemente erhalten.

Wenn Sie einige der Emissionen eines Observable ignorieren können, gibt es einige Operatoren, mit denen Sie Probenahmezeiträume erstellen und dann spezifische Werte auswählen können, die während dieser Zeiträume emittiert werden:

  • Der sample()-Operator überprüft die Ausgabe des Observables in von Ihnen festgelegten Intervallen und nimmt dann das neueste Element, das während dieses Abtastzeitraums ausgegeben wurde. Wenn Sie beispielsweise .sample(5, SECONDS) in Ihr Projekt einschließen, empfängt der Observer den letzten Wert, der in jedem Fünf-Sekunden-Intervall ausgegeben wurde.
  • Der throttleFirst()-Operator nimmt den ersten Wert, der während der Abtastperiode ausgegeben wurde. Wenn Sie beispielsweise .throttlefirst(5, SECONDS) einschließen, empfängt der Observer den ersten Wert, der in jedem Fünf-Sekunden-Intervall ausgegeben wird.
Sample OperatorSample OperatorSample Operator

Batching Emissionen mit buffer()

Wenn Sie Emissionen nicht sicher überspringen können, können Sie möglicherweise dennoch einen angeschlagenen Observer entlasten, indem Sie Emissionen in Chargen gruppieren und dann en masse weitersenden. Die Verarbeitung von Chargenemissionen ist in der Regel effizienter als die separate Verarbeitung mehrerer Emissionen, daher sollte dieser Ansatz die Verbrauchsrate verbessern.

Sie können Batch-Emissionen mit dem Operator buffer() erstellen. Hier verwenden wir buffer(), um alle über einen Zeitraum von drei Sekunden ausgegebenen Elemente zu stapeln:

Buffer operatorBuffer operatorBuffer operator

Alternativ können Sie mit buffer() eine Charge erstellen, die aus einer bestimmten Anzahl von Emissionen besteht. Hier weisen wir beispielsweise buffer() an, Emissionen in Vierergruppen zu bündeln:

Ersetzen von Observablen durch Flowables

Eine alternative Methode zur Reduzierung der Emissionen besteht darin, das Observable, das Ihnen Probleme bereitet, durch ein Flowable zu ersetzen.

In RxJava 2 hat das RxJava-Team beschlossen, das Standard-Observable in zwei Typen aufzuteilen: die reguläre Art, die wir in dieser Serie betrachtet haben, und Flowable.

Flowable funktionieren ähnlich wie Observable, jedoch mit einem großen Unterschied: Flowable senden nur so viele Elemente, wie der Beobachter anfordert. Wenn Sie ein Observable haben, das mehr Elemente aussendet, als der zugewiesene Beobachter verbrauchen kann, sollten Sie stattdessen zu einem Flowable wechseln.

Bevor Sie Flowable in Ihren Projekten verwenden können, müssen Sie die folgende Importanweisung hinzufügen:

Sie können dann Flowable erstellen, indem Sie genau die gleichen Techniken verwenden, die zum Erstellen von Observable verwendet wurden. Jeder der folgenden Codeausschnitte erstellt beispielsweise ein Flowable, das Daten ausgeben kann:

An dieser Stelle fragen Sie sich vielleicht: Warum sollte ich jemals Observable verwenden, wenn ich einfach Flowable verwenden kann und mir keine Sorgen um den Gegendruck machen muss? Die Antwort ist, dass ein Flowable mehr Overhead verursacht als ein normales Observable. Um eine leistungsstarke App zu erstellen, sollten Sie also bei Observable bleiben, es sei denn, Sie vermuten, dass Ihre Anwendung mit Gegendruck zu kämpfen hat.

Einzel

Ein Flowable ist nicht die einzige Variante von Observable, die Sie in RxJava finden, da die Bibliothek auch die Single-Klasse enthält.

Singles sind nützlich, wenn Sie nur einen Wert ausgeben müssen. In diesen Szenarien kann sich das Erstellen eines Observable als übertrieben anfühlen, aber ein Single ist so konzipiert, dass es einfach einen einzelnen Wert ausgibt und dann abgeschlossen wird, entweder durch Aufrufen von:

  • onSuccess(): Der Single gibt seinen einzigen Wert aus.
  • onError(): Wenn der Single sein Item nicht ausgeben kann, übergibt er dieser Methode das resultierende Throwable.

Ein Single ruft nur eine dieser Methoden auf und wird dann sofort beendet.

Sehen wir uns ein Beispiel für eine Single in Aktion an – auch hier verwenden wir Code wieder, um Zeit zu sparen:

Führen Sie Ihr Projekt auf einem AVD oder einem physischen Android-Gerät aus, und Sie sehen die folgende Ausgabe im Logcat-Monitor von Android Studio:

Check the Singles output in Android Studios Logcat MonitorCheck the Singles output in Android Studios Logcat MonitorCheck the Singles output in Android Studios Logcat Monitor

Wenn Sie Ihre Meinung ändern und zu irgendeinem Zeitpunkt ein Single in ein Observable umwandeln möchten, dann bietet RxJava wieder alle Operatoren, die Sie benötigen, einschließlich:

  • mergeWith(): Fügt mehrere Singles zu einem einzigen Observable zusammen.
  • concatWith(): Verkettet die von mehreren Singles emittierten Elemente, um eine Observable-Emission zu bilden.
  • toObservable(): Konvertiert ein Single in ein Observable, das das ursprünglich vom Single ausgegebene Element ausgibt und dann abschließt.

Zusammenfassung

In diesem Beitrag haben wir einige RxJava-Operatoren untersucht, mit denen Sie mehrere Threads erstellen und verwalten können, ohne die Komplexität und das Fehlerpotenzial, die traditionell mit Multithreading auf Android einhergehen. Wir haben auch gesehen, wie Sie die RxAndroid-Bibliothek verwenden können, um mithilfe einer einzigen Codezeile mit dem wichtigen Haupt-UI-Thread von Android zu kommunizieren und sicherzustellen, dass der Gegendruck in Ihrer Anwendung nicht zum Problem wird.

Wir haben die RxAndroid-Bibliothek in dieser Serie ein paar Mal berührt, aber diese Bibliothek ist vollgepackt mit Android-spezifischen RxJava-Bindungen, die bei der Arbeit mit RxJava auf der Android-Plattform von unschätzbarem Wert sein können Schauen Sie sich die RxAndroid-Bibliothek viel genauer an.

Sehen Sie sich bis dahin einige unserer anderen Beiträge zum Codieren für Android an!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.