Dies ist ein Repository mit praktischen Beispielen für die Verwendung von RxJava mit Android. Normalerweise befindet es sich im ständigen Zustand „Work in Progress“ (WIP).
Ich habe auch Vorträge über das Erlernen von Rx gehalten und dabei viele der in diesem Repo aufgeführten Beispiele verwendet.
using
)Eine häufige Anforderung besteht darin, langwierige, I/O-intensive Vorgänge auf einen Hintergrundthread (Nicht-UI-Thread) auszulagern und die Ergebnisse nach Abschluss an den UI-/Hauptthread zurückzugeben. Dies ist eine Demo, wie lang laufende Vorgänge in einen Hintergrundthread verlagert werden können. Nachdem der Vorgang abgeschlossen ist, kehren wir zum Hauptthread zurück. Alle nutzen RxJava! Betrachten Sie dies als Ersatz für AsyncTasks.
Der lange Vorgang wird durch einen blockierenden Thread.sleep-Aufruf simuliert (da dies in einem Hintergrundthread erfolgt, wird unsere Benutzeroberfläche nie unterbrochen).
Um dieses Beispiel wirklich glänzen zu sehen. Klicken Sie mehrmals auf die Schaltfläche und sehen Sie, dass das Klicken auf die Schaltfläche (bei dem es sich um einen UI-Vorgang handelt) niemals blockiert wird, da der lange Vorgang nur im Hintergrund ausgeführt wird.
Dies ist eine Demo, wie Ereignisse mithilfe der „Puffer“-Operation akkumuliert werden können.
Es wird eine Schaltfläche bereitgestellt, und wir sammeln über einen bestimmten Zeitraum hinweg die Anzahl der Klicks auf diese Schaltfläche und geben dann die Endergebnisse aus.
Wenn Sie die Taste einmal drücken, erhalten Sie eine Meldung, dass die Taste einmal gedrückt wurde. Wenn Sie innerhalb von 2 Sekunden fünfmal hintereinander darauf drücken, erhalten Sie ein einzelnes Protokoll, das besagt, dass Sie diese Taste fünfmal gedrückt haben (im Gegensatz zu fünf einzelnen Protokollen mit der Aufschrift „Taste einmal gedrückt“).
Notiz:
Wenn Sie nach einer narrensichereren Lösung suchen, die „kontinuierliche“ Taps statt nur der Anzahl der Taps innerhalb einer Zeitspanne akkumuliert, schauen Sie sich die EventBus-Demo an, in der eine Kombination aus publish
und buffer
verwendet wird. Für eine ausführlichere Erklärung können Sie sich auch diesen Blogbeitrag ansehen.
Dies ist eine Demonstration, wie Ereignisse so geschluckt werden können, dass nur das letzte respektiert wird. Ein typisches Beispiel hierfür sind Felder mit sofortigen Suchergebnissen. Während Sie das Wort „Bruce Lee“ eingeben, möchten Sie keine Suchanfragen nach „B“, „Br“, „Bru“, „Bruce“, „Bruce“, „Bruce L“ usw. ausführen. Warten Sie stattdessen lieber ein paar Augenblicke, um sicherzustellen, dass der Benutzer sicher ist hat das ganze Wort zu Ende getippt und ruft dann „Bruce Lee“ an.
Während Sie in das Eingabefeld tippen, werden nicht bei jedem einzelnen Zeichenwechsel Protokollmeldungen ausgegeben, sondern es wird nur das zuletzt ausgegebene Ereignis (also die Eingabe) ausgewählt und protokolliert.
Dies ist die Debounce/ThrottleWithTimeout-Methode in RxJava.
Retrofit von Square ist eine erstaunliche Bibliothek, die bei der einfachen Vernetzung hilft (auch wenn Sie noch nicht auf RxJava umgestiegen sind, sollten Sie es sich unbedingt ansehen). Es funktioniert sogar noch besser mit RxJava und dies sind Beispiele für die Nutzung der GitHub-API, direkt aus dem Vortrag des Android-Halbgott-Entwicklers Jake Wharton bei Netflix. Sie können den Vortrag unter diesem Link ansehen. Meine Motivation, RxJava zu verwenden, war übrigens die Teilnahme an diesem Vortrag bei Netflix.
(Hinweis: Sie werden das GitHub-API-Kontingent höchstwahrscheinlich ziemlich schnell erreichen. Senden Sie also ein OAuth-Token als Parameter, wenn Sie diese Beispiele weiterhin häufig ausführen möchten.)
Die automatische Aktualisierung von Ansichten ist eine ziemlich coole Sache. Wenn Sie sich schon einmal mit Angular JS beschäftigt haben, verfügen sie über ein ziemlich raffiniertes Konzept namens „bidirektionale Datenbindung“. Wenn also ein HTML-Element an ein Modell-/Entitätsobjekt gebunden ist, „lauscht“ es ständig auf Änderungen an dieser Entität und aktualisiert seinen Status automatisch basierend auf dem Modell. Mit der Technik in diesem Beispiel könnten Sie möglicherweise ganz einfach ein Muster wie das Presentation View Model-Muster verwenden.
Während das Beispiel hier recht rudimentär ist, ist die Technik, mit der die doppelte Bindung mithilfe eines Publish Subject
erreicht wird, viel interessanter.
Dies ist ein Beispiel für eine Abfrage mit RxJava-Schedulern. Dies ist in Fällen nützlich, in denen Sie einen Server ständig abfragen und möglicherweise neue Daten erhalten möchten. Der Netzwerkaufruf wird „simuliert“, sodass eine Verzögerung erzwungen wird, bevor eine resultierende Zeichenfolge zurückgegeben wird.
Hierzu gibt es zwei Varianten:
Das zweite Beispiel ist im Grunde eine Variante des Exponential Backoff.
Anstatt ein RetryWithDelay zu verwenden, verwenden wir hier ein RepeatWithDelay. Um den Unterschied zwischen Retry(When) und Repeat(When) zu verstehen, würde ich Dans fantastischen Beitrag zu diesem Thema empfehlen.
Ein alternativer Ansatz zum verzögerten Polling ohne die Verwendung von repeatWhen
wäre die Verwendung verketteter, verschachtelter Verzögerungsobservablen. Siehe startExecutingWithExponentialBackoffDelay im ExponentialBackOffFragment-Beispiel.
Exponentielles Backoff ist eine Strategie, bei der wir basierend auf dem Feedback einer bestimmten Ausgabe die Geschwindigkeit eines Prozesses ändern (normalerweise reduzieren wir die Anzahl der Wiederholungsversuche oder erhöhen die Wartezeit vor dem erneuten Versuch oder der erneuten Ausführung eines bestimmten Prozesses).
Mit Beispielen ergibt das Konzept mehr Sinn. RxJava macht es (relativ) einfach, eine solche Strategie umzusetzen. Mein Dank geht an Mike für den Vorschlag der Idee.
Angenommen, Sie haben einen Netzwerkfehler. Eine vernünftige Strategie wäre, Ihren Netzwerkanruf NICHT jede Sekunde erneut zu versuchen. Es wäre stattdessen klug (nein ... elegant!), es mit zunehmenden Verzögerungen noch einmal zu versuchen. Sie versuchen also bei Sekunde 1, den Netzwerkaufruf auszuführen, keine Würfel? Versuchen Sie es nach 10 Sekunden... negativ? Versuchen Sie es nach 20 Sekunden, kein Cookie? Versuchen Sie es nach 1 Minute. Wenn das Ding immer noch nicht funktioniert, müssen Sie das Netzwerk aufgeben, yo!
Wir simulieren dieses Verhalten mit RxJava mit dem retryWhen
-Operator.
RetryWithDelay
Codeausschnitt mit freundlicher Genehmigung:
Schauen Sie sich auch das Polling-Beispiel an, in dem wir einen sehr ähnlichen exponentiellen Backoff-Mechanismus verwenden.
Eine andere Variante der exponentiellen Backoff-Strategie besteht darin, eine Operation für eine bestimmte Anzahl von Malen, jedoch mit verzögerten Intervallen, auszuführen. Sie führen also eine bestimmte Operation in 1 Sekunde aus, dann führen Sie sie in 10 Sekunden erneut aus, und dann führen Sie die Operation in 20 Sekunden aus. Nach insgesamt 3 Malen hören Sie mit der Ausführung auf.
Die Simulation dieses Verhaltens ist tatsächlich viel einfacher als der vorherige Wiederholungsmechanismus. Sie können dazu eine Variante des delay
verwenden.
.combineLatest
)Vielen Dank an Dan Lew, der mir diese Idee im fragmentierten Podcast gegeben hat – Folge Nr. 4 (ca. 4:30 Uhr).
.combineLatest
können Sie den Zustand mehrerer Observablen gleichzeitig und kompakt an einem einzigen Ort überwachen. Das gezeigte Beispiel zeigt, wie Sie .combineLatest
verwenden können, um ein Basisformular zu validieren. Es gibt drei Haupteingaben, damit dieses Formular als „gültig“ gilt (eine E-Mail-Adresse, ein Passwort und eine Nummer). Das Formular wird gültig (der Text unten wird blau :P), sobald alle Eingaben gültig sind. Ist dies nicht der Fall, wird ein Fehler für die ungültigen Eingaben angezeigt.
Wir haben drei unabhängige Observables, die die Text-/Eingabeänderungen für jedes der Formularfelder verfolgen (RxAndroids WidgetObservable
ist praktisch, um die Textänderungen zu überwachen). Nachdem bei allen drei Eingaben eine Ereignisänderung festgestellt wurde, wird das Ergebnis „kombiniert“ und das Formular auf Gültigkeit überprüft.
Beachten Sie, dass die Func3
Funktion, die die Gültigkeit prüft, erst dann aktiviert wird, wenn ALLE 3 Eingänge ein Textänderungsereignis empfangen haben.
Der Wert dieser Technik wird deutlicher, wenn Sie mehr Eingabefelder in einem Formular haben. Wenn man es anders mit einer Reihe boolescher Werte behandelt, wird der Code unübersichtlich und schwer verständlich. Aber mit .combineLatest
ist die gesamte Logik in einem schönen, kompakten Codeblock konzentriert (ich verwende immer noch boolesche Werte, aber das diente dazu, das Beispiel besser lesbar zu machen).
Wir haben zwei Quell-Observables: einen Festplatten-Cache (schnell) und einen Netzwerkaufruf (frisch). Normalerweise ist das Festplatten-Observable viel schneller als das Netzwerk-Observable. Aber um die Funktionsweise zu demonstrieren, haben wir auch einen gefälschten „langsameren“ Festplatten-Cache verwendet, um zu sehen, wie sich die Operatoren verhalten.
Dies wird anhand von 4 Techniken demonstriert:
.concat
.concatEager
.merge
.publish
Selektor + Merge + TakeUntilDie vierte Technik ist wahrscheinlich die, die Sie irgendwann anwenden möchten, aber es ist interessant, den Fortschritt der Techniken durchzugehen, um zu verstehen, warum.
concat
ist großartig. Es ruft Informationen vom ersten Observable (in unserem Fall Festplatten-Cache) und dann vom nachfolgenden Netzwerk-Observable ab. Da der Festplatten-Cache vermutlich schneller ist, sieht alles gut aus und der Festplatten-Cache wird schnell geladen, und sobald der Netzwerkaufruf beendet ist, tauschen wir die „frischen“ Ergebnisse aus.
Das Problem bei concat
besteht darin, dass das nachfolgende Observable erst dann startet, wenn das erste Observable abgeschlossen ist. Das kann ein Problem sein. Wir möchten, dass alle Observablen gleichzeitig starten, aber die Ergebnisse auf die von uns erwartete Weise liefern. Zum Glück hat RxJava concatEager
eingeführt, das genau das tut. Es startet beide Observablen, puffert jedoch das Ergebnis des letzteren, bis das erstere Observable beendet ist. Dies ist eine durchaus praktikable Option.
Manchmal möchten Sie jedoch einfach sofort mit der Anzeige der Ergebnisse beginnen. Angenommen, das erste Observable braucht (aus irgendeinem seltsamen Grund) sehr lange, um alle seine Elemente zu durchlaufen, selbst wenn die ersten paar Elemente des zweiten Observablen durch den Draht gekommen sind, wird es zwangsweise in die Warteschlange gestellt. Sie möchten nicht unbedingt auf ein Observable „warten“. In diesen Situationen könnten wir den merge
-Operator verwenden. Es verschachtelt Elemente, wenn sie ausgegeben werden. Das funktioniert großartig und die Ergebnisse werden sofort ausgespuckt, sobald sie angezeigt werden.
Ähnlich wie beim concat
-Operator treten keine Probleme auf, wenn Ihr erstes Observable immer schneller als das zweite Observable ist. Das Problem bei merge
ist jedoch: Wenn aus irgendeinem seltsamen Grund ein Element vom Cache oder einem langsameren Observable nach dem neueren/frischeren Observable ausgegeben wird, wird der neuere Inhalt überschrieben. Klicken Sie im Beispiel auf die Schaltfläche „MERGE (SLOWER DISK)“, um dieses Problem in Aktion zu sehen. Die Beiträge von @JakeWharton und @swankjesse gehen auf 0! In der realen Welt könnte dies schlecht sein, da dies bedeuten würde, dass die neuen Daten durch veraltete Festplattendaten überschrieben würden.
Um dieses Problem zu lösen, können Sie merge in Kombination mit dem äußerst raffinierten publish
-Operator verwenden, der einen „Selektor“ einbezieht. Ich habe über diese Verwendung in einem Blogbeitrag geschrieben, aber ich muss mich bei Jedi JW dafür bedanken, dass er an diese Technik erinnert hat. Wir publish
das Netzwerk-Observable und stellen ihm einen Selektor zur Verfügung, der mit der Ausgabe aus dem Festplatten-Cache beginnt, bis zu dem Punkt, an dem das Netzwerk-Observable mit der Ausgabe beginnt. Sobald das Netzwerk-Observable mit der Ausgabe beginnt, ignoriert es alle Ergebnisse vom Festplatten-Observable. Das ist perfekt und löst alle Probleme, die wir haben könnten.
Zuvor habe ich den merge
-Operator verwendet, aber das Problem des Überschreibens von Ergebnissen konnte ich durch die Überwachung von „resultAge“ lösen. Sehen Sie sich das alte PseudoCacheMergeFragment
-Beispiel an, wenn Sie neugierig auf diese alte Implementierung sind.
Dies ist ein supereinfaches und unkompliziertes Beispiel, das Ihnen zeigt, wie Sie timer
, interval
und delay
von RxJava verwenden, um eine Reihe von Fällen zu behandeln, in denen Sie eine Aufgabe in bestimmten Intervallen ausführen möchten. Sagen Sie grundsätzlich NEIN zu Android TimerTask
s.
Hier gezeigte Fälle:
Es gibt begleitende Blogbeiträge, die die Details dieser Demo viel besser erklären:
Eine häufig gestellte Frage bei der Verwendung von RxJava in Android lautet: „Wie kann ich die Arbeit eines Observablen fortsetzen, wenn eine Konfigurationsänderung auftritt (Aktivitätsrotation, Änderung des Sprachgebietsschemas usw.)?“
Dieses Beispiel zeigt Ihnen eine Strategie, nämlich. Verwendung beibehaltener Fragmente. Nachdem ich vor einiger Zeit diesen fantastischen Beitrag von Alex Lockwood gelesen hatte, begann ich, beibehaltene Fragmente als „Arbeiterfragmente“ zu verwenden.
Drücken Sie die Starttaste und drehen Sie den Bildschirm nach Herzenslust. Sie werden sehen, wie das Observable dort weitergeht, wo es aufgehört hat.
Es gibt gewisse Eigenheiten hinsichtlich der „Aktivität“ der in diesem Beispiel verwendeten beobachtbaren Quelle. Schauen Sie sich meinen Blog-Beitrag an, in dem ich die Einzelheiten erkläre.
Seitdem habe ich dieses Beispiel mit einem alternativen Ansatz umgeschrieben. Während der ConnectedObservable
-Ansatz funktionierte, dringt er in die Bereiche des „Multicasting“ ein, was schwierig sein kann (Thread-Sicherheit, .refcount usw.). Die Themen hingegen sind weitaus einfacher. Hier können Sie sehen, wie es mit einem Subject
umgeschrieben wurde.
Ich habe einen weiteren Blog-Beitrag darüber geschrieben, wie man über Themen nachdenkt, in dem ich auf einige Einzelheiten eingehe.
Volley ist eine weitere Netzwerkbibliothek, die Google auf der IO '13 vorgestellt hat. Ein freundlicher Github-Bürger hat dieses Beispiel beigesteuert, damit wir wissen, wie man Volley in RxJava integriert.
Ich nutze hier die einfache Verwendung eines Subjekts. Ehrlich gesagt gibt es keinen guten Grund, Rx zu verwenden und die Dinge zu verkomplizieren, wenn Ihre Artikel nicht bereits über ein Observable
heruntergekommen sind (z. B. durch Retrofit oder eine Netzwerkanfrage).
In diesem Beispiel wird grundsätzlich die Seitenzahl an einen Betreff gesendet, und der Betreff übernimmt das Hinzufügen der Elemente. Beachten Sie die Verwendung von concatMap
und die Rückgabe eines Observable<List>
von _itemsFromNetworkCall
.
Für den Kick habe ich auch ein PaginationAutoFragment
-Beispiel eingefügt, das „automatisch paginiert“, ohne dass wir einen Knopf drücken müssen. Es sollte einfach zu befolgen sein, wenn Sie verstanden haben, wie das vorherige Beispiel funktioniert.
Hier sind einige andere ausgefallene Implementierungen (obwohl es mir Spaß gemacht hat, sie zu lesen, habe ich sie nicht für meine reale App verwendet, weil ich persönlich nicht glaube, dass es notwendig ist):
Das folgende ASCII-Diagramm drückt die Absicht unseres nächsten Beispiels mit Bravour aus. f1,f2,f3,f4,f5 sind im Wesentlichen Netzwerkaufrufe, die, wenn sie durchgeführt werden, ein Ergebnis zurückgeben, das für eine zukünftige Berechnung benötigt wird.
(flatmap)
f1 ___________________ f3 _______
(flatmap) | (zip)
f2 ___________________ f4 _______| ___________ final output
|
____________ f5 _______|
Der Code für dieses Beispiel wurde bereits von einem Herrn Skehlet im Internet geschrieben. Gehen Sie zum Kern, um den Code zu erhalten. Es ist in reinem Java (6) geschrieben und daher ziemlich verständlich, wenn Sie die vorherigen Beispiele verstanden haben. Ich werde es hier noch einmal hervorbringen, wenn es die Zeit erlaubt oder mir andere überzeugende Beispiele ausgehen.
Dies ist ein einfaches Beispiel, das die Verwendung des .timeout
-Operators demonstriert. Schaltfläche 1 schließt die Aufgabe vor der Zeitüberschreitungsbeschränkung ab, während Schaltfläche 2 einen Zeitüberschreitungsfehler erzwingt.
Beachten Sie, wie wir ein benutzerdefiniertes Observable bereitstellen können, das angibt, wie bei einer Timeout-Ausnahme reagiert werden soll.
using
) Der using
Betreiber ist vergleichsweise weniger bekannt und für Google bekanntermaßen schwierig. Es ist eine schöne API, die dabei hilft, eine (kostspielige) Ressource einzurichten, zu verwenden und dann sauber zu entsorgen.
Das Schöne an diesem Operator ist, dass er einen Mechanismus bietet, um potenziell kostspielige Ressourcen in einem begrenzten Umfang zu nutzen. mit -> einrichten, verwenden und entsorgen. Denken Sie an DB-Verbindungen (wie Realm-Instanzen), Socket-Verbindungen, Thread-Sperren usw.
Multicasting in Rx ist wie eine dunkle Kunst. Nicht viele Leute wissen, wie man es ohne Bedenken schafft. In diesem Beispiel werden zwei Abonnenten verknüpft (in Form von Schaltflächen) und Sie können Abonnenten zu unterschiedlichen Zeitpunkten hinzufügen/entfernen und sehen, wie sich die verschiedenen Operatoren unter diesen Umständen verhalten.
Bei der Quellobservable handelt es sich um eine Timer-( interval
)Observable. Der Grund für die Wahl bestand darin, absichtlich eine nicht terminierende Observable auszuwählen, damit Sie testen/bestätigen können, ob Ihr Multicast-Experiment Lecks verursacht.
Außerdem habe ich bei 360|Andev einen ausführlichen Vortrag über Multicasting gehalten. Wenn Sie Lust und Zeit haben, empfehle ich Ihnen dringend, sich zuerst diesen Vortrag anzusehen (insbesondere das Segment der Multicast-Operator-Permutation) und dann mit dem Beispiel hier herumzuspielen.
Alle Beispiele hier wurden migriert, um RxJava 2.X zu verwenden.
In einigen Fällen verwenden wir die Interop-Bibliothek von David Karnok, da bestimmte Bibliotheken wie RxBindings, RxRelays, RxJava-Math usw. noch nicht auf 2.x portiert wurden.
Ich versuche sicherzustellen, dass die Beispiele nicht übermäßig konstruiert sind, sondern einen realen Anwendungsfall widerspiegeln. Wenn Sie ähnliche nützliche Beispiele haben, die die Verwendung von RxJava demonstrieren, können Sie gerne eine Pull-Anfrage senden.
Ich beschäftige mich auch mit RxJava. Wenn Sie also der Meinung sind, dass es eine bessere Möglichkeit gibt, eines der oben genannten Beispiele umzusetzen, öffnen Sie ein Problem und erklären Sie, wie das geht. Noch besser: Senden Sie eine Pull-Anfrage.
Rx-Threading ist eine chaotische Angelegenheit. Um zu helfen, verwendet dieses Projekt YourKit-Tools zur Analyse.
YourKit unterstützt Open-Source-Projekte mit innovativen und intelligenten Tools zur Überwachung und Profilierung von Java-Anwendungen. YourKit ist der Entwickler von YourKit Java Profiler.
Lizenziert unter der Apache-Lizenz, Version 2.0 (die „Lizenz“). Eine Kopie der Lizenz erhalten Sie unter
http://www.apache.org/licenses/LICENSE-2.0
Sofern nicht durch geltendes Recht vorgeschrieben oder schriftlich vereinbart, wird die im Rahmen der Lizenz vertriebene Software „WIE BESEHEN“ und OHNE GEWÄHRLEISTUNGEN ODER BEDINGUNGEN JEGLICHER ART, weder ausdrücklich noch stillschweigend, vertrieben. Die spezifische Sprache, die die Berechtigungen und Einschränkungen im Rahmen der Lizenz regelt, finden Sie in der Lizenz.
Sie erklären sich damit einverstanden, dass alle Beiträge zu diesem Repository in Form von Korrekturen, Pull-Requests, neuen Beispielen usw. der oben genannten Lizenz folgen.