
Ich arbeite an einer neuen Anwendung, die wie üblich mit dem Backend-Dienst kommuniziert, um Daten über die API zu empfangen. In diesem Beispiel werde ich eine Suchfunktion entwickeln, deren eine Funktion die sofortige Suche direkt bei der Texteingabe ist.
Sofortige Suche
Nichts kompliziertes, denkst du? Sie müssen nur die onTextChange
auf der Seite platzieren (höchstwahrscheinlich in der Symbolleiste), den Ereignishandler onTextChange
verbinden und die Suche durchführen. Also hier ist was ich getan habe:
override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
Aber hier ist das Problem. Da ich während der Eingabe eine Suche implementieren muss, onQueryTextChange()
ich mich bei jedem onQueryTextChange()
eines onQueryTextChange()
an die API, um die ersten Ergebnisse zu erhalten. Die Protokolle lauten wie folgt:
D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TEST D/MainActivity: onQueryTextSubmit: TEST
Trotz der Tatsache, dass ich nur meine Anfrage drucke, gibt es fünf API-Aufrufe, von denen jeder eine Suche durchführt. In der Cloud müssen Sie beispielsweise für jeden Aufruf der API bezahlen. Wenn ich meine Anfrage eingebe, brauche ich daher eine kleine Verzögerung, bevor ich sie sende, damit am Ende nur ein API-Aufruf erfolgt.
Angenommen, ich möchte etwas anderes finden. Ich lösche TEST und gebe andere Zeichen ein:
D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: D/MainActivity: onQueryTextChange: S D/MainActivity: onQueryTextChange: SO D/MainActivity: onQueryTextChange: SOM D/MainActivity: onQueryTextChange: SOME D/MainActivity: onQueryTextChange: SOMET D/MainActivity: onQueryTextChange: SOMETH D/MainActivity: onQueryTextChange: SOMETHI D/MainActivity: onQueryTextChange: SOMETHIN D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING E D/MainActivity: onQueryTextChange: SOMETHING EL D/MainActivity: onQueryTextChange: SOMETHING ELS D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextSubmit: SOMETHING ELSE
Es gibt 20 API-Aufrufe! Eine kleine Verzögerung verringert die Anzahl dieser Anrufe. Ich möchte auch Duplikate entfernen, damit der zugeschnittene Text nicht zu wiederholten Anfragen führt. Ich möchte wahrscheinlich auch einige Elemente herausfiltern. Benötigen Sie beispielsweise die Fähigkeit, ohne die eingegebenen Zeichen zu suchen oder nach einem Zeichen zu suchen?
Reaktive Programmierung
Es gibt verschiedene Möglichkeiten, was als nächstes zu tun ist, aber im Moment möchte ich mich auf eine Technik konzentrieren, die allgemein als reaktive Programmierung und die RxJava-Bibliothek bekannt ist. Als ich zum ersten Mal auf reaktive Programmierung stieß, sah ich die folgende Beschreibung:
ReactiveX ist eine API, die mit asynchronen Strukturen arbeitet und Datenströme oder Ereignisse mithilfe von Kombinationen aus Observer- und Iterator-Mustern sowie funktionalen Programmierfunktionen bearbeitet.
Diese Definition erklärt die Natur und die Stärken von ReactiveX nicht vollständig. Und wenn es erklärt, dann nur für diejenigen, die bereits mit den Prinzipien dieses Rahmens vertraut sind. Ich habe auch solche Diagramme gesehen:

Das Diagramm erklärt die Rolle des Bedieners, versteht jedoch das Wesentliche nicht vollständig. Lassen Sie uns also sehen, ob ich dieses Diagramm anhand eines einfachen Beispiels klarer erklären kann.
Lassen Sie uns zuerst unser Projekt vorbereiten. Sie benötigen eine neue Bibliothek in der Datei build.gradle
Ihrer Anwendung:
implementation "io.reactivex.rxjava2:rxjava:2.1.14"
Denken Sie daran, die Projektabhängigkeiten zu synchronisieren, um die Bibliothek zu laden.
Schauen wir uns nun eine neue Lösung an. Mit der alten Methode habe ich auf die API zugegriffen, als ich jedes neue Zeichen eingegeben habe. Mit der neuen Methode werde ich ein Observable
erstellen:
override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
Dieser Code macht genau das Gleiche wie der alte Code. Die Protokolle lauten wie folgt:
D/MainActivity: subscriber: T D/MainActivity: subscriber: TE D/MainActivity: subscriber: TES D/MainActivity: subscriber: TEST D/MainActivity: subscriber: TEST
Der Hauptunterschied zwischen der Verwendung der neuen Technik ist jedoch das Vorhandensein eines reaktiven Stroms - beobachtbar. Der Texthandler (oder in diesem Fall der Anforderungshandler) sendet die Elemente mit der Methode onNext()
an den Stream. Und Observable
hat Abonnenten, die diese Elemente verarbeiten.
Wir können vor dem Abonnieren eine Methodenkette erstellen, um die Liste der zu verarbeitenden Zeichenfolgen zu reduzieren. Zunächst wird der gesendete Text immer in Kleinbuchstaben geschrieben und am Anfang und Ende der Zeile stehen keine Leerzeichen:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
Ich schneide Methoden, um den wichtigsten Teil zu zeigen. Jetzt sind die gleichen Protokolle wie folgt:
D/MainActivity: subscriber: t D/MainActivity: subscriber: te D/MainActivity: subscriber: tes D/MainActivity: subscriber: test D/MainActivity: subscriber: test
Wenden wir nun eine Verzögerung von 250 ms an und erwarten mehr Inhalt:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .subscribe { text -> Log.d(TAG, "subscriber: $text" }
Löschen Sie abschließend den doppelten Stream, sodass nur die erste eindeutige Anforderung verarbeitet wird. Nachfolgende identische Anforderungen werden ignoriert:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .subscribe { text -> Log.d(TAG, "subscriber: $text" }
Hinweis perev. In diesem Fall ist es sinnvoller, den Operator distinctUntilChanged()
, da sonst bei wiederholter Suche nach einer Zeichenfolge die Abfrage einfach ignoriert wird. Und wenn Sie eine solche Suche implementieren, ist es sinnvoll, nur auf die letzte erfolgreiche Anforderung zu achten und die neue zu ignorieren, wenn sie mit der vorherigen identisch ist.
Am Ende filtern wir leere Abfragen heraus:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
An diesem Punkt werden Sie feststellen, dass nur eine (oder möglicherweise zwei) Nachricht in den Protokollen angezeigt wird, was auf weniger API-Aufrufe hinweist. In diesem Fall funktioniert die Anwendung weiterhin ordnungsgemäß. Darüber hinaus führen Fälle, in denen Sie etwas eingeben, dann aber löschen und erneut eingeben, zu weniger Aufrufen der API.
Abhängig von Ihren Zielen können Sie dieser Pipeline viele weitere verschiedene Operatoren hinzufügen. Ich glaube, dass sie sehr nützlich für die Arbeit mit Eingabefeldern sind, die mit der API interagieren. Der vollständige Code lautet wie folgt:
Jetzt kann ich die Protokollnachricht durch einen Aufruf des ViewModel ersetzen, um einen API-Aufruf zu initiieren. Dies ist jedoch ein Thema für einen anderen Artikel.
Fazit
Mit dieser einfachen Technik zum Umschließen von Textelementen in Observable
und mit RxJava können Sie die Anzahl der API-Aufrufe reduzieren, die zum Ausführen von Serveroperationen erforderlich sind, und die Reaktionsfähigkeit Ihrer Anwendung verbessern. In diesem Artikel haben wir nur einen kleinen Teil der gesamten Welt von RxJava behandelt. Daher hinterlasse ich Ihnen Links für weitere Informationen zu diesem Thema:
- Dan Lew Grokking RxJava (dies ist die Seite, die mir geholfen hat, mich in die richtige Richtung zu bewegen).
- ReactiveX-Site (Ich beziehe mich beim Erstellen einer Pipeline häufig auf diese Site).