Beobachtbare Dienstprogrammoperatoren in RxJava

Observable Utility Operators in RxJava

1. Überblick

In diesem Artikel werden einige Dienstprogrammoperatoren für die Arbeit mitObservables in RxJava und die Implementierung benutzerdefinierter Operatoren beschrieben.

An operator is a function that takes and alters the behavior of an upstream Observable<T> and returns a downstream Observable<R> or Subscriber, wobei die Typen T und R möglicherweise gleich sind oder nicht.

Operatoren verpacken vorhandeneObservablesund erweitern sie normalerweise durch Abfangen von Abonnements. Das mag kompliziert klingen, ist aber tatsächlich recht flexibel und nicht so schwer zu erfassen.

2. Do

Es gibt mehrere Aktionen, die die Lebenszyklusereignisse vonObservableändern können.

Der Operator doOnNext ändert die QuelleObservable so that it invokes an action when the onNext is called .__

The doOnCompleted operator registers an action which is called if the resulting Observable terminates normally, wobeiObserveronCompleted Methode: aufgerufen wird

Observable.range(1, 10)
  .doOnNext(r -> receivedTotal += r)
  .doOnCompleted(() -> result = "Completed")
  .subscribe();

assertTrue(receivedTotal == 55);
assertTrue(result.equals("Completed"));

Der OperatordoOnEach ändert die QuelleObservableo, dass er für jedes Element einObserver benachrichtigt und einen Rückruf erstellt, der jedes Mal aufgerufen wird, wenn ein Element ausgegeben wird.

Der OperatordoOnSubscribe registriert eine Aktion, die immer dann aufgerufen wird, wenn einObserver die resultierendenObservable abonniert.

Es gibt auchdoOnUnsubscribe operator, was das Gegenteil vondoOnSubscribe: bewirkt

Observable.range(1, 10)
  .doOnEach(new Observer() {
      @Override
      public void onCompleted() {
          System.out.println("Complete");
      }
      @Override
      public void onError(Throwable e) {
          e.printStackTrace();
      }
      @Override
      public void onNext(Integer value) {
          receivedTotal += value;
      }
  })
  .doOnSubscribe(() -> result = "Subscribed")
  .subscribe();
assertTrue(receivedTotal == 55);
assertTrue(result.equals("Subscribed"));

Wenn einObservable mit einem Fehler abgeschlossen ist, können wir den OperatordoOnError verwenden, um eine Aktion auszuführen.

DoOnTerminate operator registriert eine Aktion, die aufgerufen wird, wenn ein Observable erfolgreich oder mit einem Fehler abgeschlossen wird:

thrown.expect(OnErrorNotImplementedException.class);
Observable.empty()
  .single()
  .doOnError(throwable -> { throw new RuntimeException("error");})
  .doOnTerminate(() -> result += "doOnTerminate")
  .doAfterTerminate(() -> result += "_doAfterTerminate")
  .subscribe();
assertTrue(result.equals("doOnTerminate_doAfterTerminate"));

Es gibt auch einFinallyDo operator – which was deprecated in favor of doAfterTerminate. It registers an action when an Observable completes.

3. ObserveOn vsSubscribeOn

Standardmäßig wird einObservable zusammen mit der Operatorkette auf demselben Thread ausgeführt, auf dem seineSubscribe-Methode aufgerufen wird.

Der Soperator[.operator]#ObserveOn #gibt ein anderesScheduler an, das derObservable zum Senden von Benachrichtigungen anObservers: verwendet

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .observeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

Wir sehen, dass Elemente inmain thread erzeugt und bis zum ersten Aufruf vonmap verschoben wurden.

Danach leiteten dieobserveOn die Verarbeitung aufcomputation thread um, was bei der Verarbeitung vonmap und den letztenSubscriber. verwendet wurde

One problem that may arise with observeOn is the bottom stream can produce emissions faster than the top stream can process them. Dies kann Probleme mitbackpressure verursachen, die wir möglicherweise berücksichtigen müssen.

Um anzugeben, auf welchenScheduler dieObservable arbeiten sollen, können wir den OperatorsubscribeOn verwenden:

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .subscribeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

SubscribeOn weist die QuelleObservable an, welchen Thread zum Ausgeben von Elementen verwendet werden soll. Nur dieser Thread überträgt Elemente anSubscriber. Es kann an einer beliebigen Stelle im Stream platziert werden, da es nur das Abonnement betrifft.

Tatsächlich können wir nur einsubscribeOn verwenden, aber wir können eine beliebige Anzahl vonobserveOn-Operatoren haben. MitobserveOn. können wir Emissionen problemlos von einem Thread in einen anderen umschalten

4. Single undSingleOrDefault

Der OperatorSingle gibtObservable zurück, das das von der QuelleObservable: ausgegebene Einzelelement ausgibt

Observable.range(1, 1)
  .single()
  .subscribe(i -> receivedTotal += i);
assertTrue(receivedTotal == 1);

Wenn die QuelleObservable null oder mehr als ein Element erzeugt, wird eine Ausnahme ausgelöst:

Observable.empty()
  .single()
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

Andererseits ist der OperatorSingleOrDefaultSingle, sehr ähnlich, was bedeutet, dass er auch einObservable zurückgibt, das das einzelne Element von der Quelle ausgibt. Zusätzlich können wir jedoch einen Standardwert angeben ::

Observable.empty()
  .singleOrDefault("Default")
  .subscribe(i -> result +=i);
assertTrue(result.equals("Default"));

Wenn die Quelle vonObservablejedoch mehr als ein Element ausgibt, wird immer noch einIllegalArgumentExeption: ausgegeben

Observable.range(1, 3)
  .singleOrDefault(5)
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

S einfache Schlussfolgerung:

  • Wenn erwartet wird, dass die QuelleObservable kein oder nur ein Element enthält, sollteSingleOrDefault verwendet werden

  • Wenn es sich um möglicherweise mehr als ein Element handelt, das in unseren`Observable`ausgegeben wird, und wir nur den ersten oder den letzten Wert ausgeben möchten, können wir andere Operatoren wiefirst oderlast verwenden

5. Timestamp

The Timestamp operator attaches a timestamp to each item emitted by the source Observable, bevor das Element in seiner eigenen Reihenfolge erneut ausgegeben wird. Der Zeitstempel gibt an, zu welcher Uhrzeit das Objekt gesendet wurde:

Observable.range(1, 10)
  .timestamp()
  .map(o -> result = o.getClass().toString() )
  .last()
  .subscribe();

assertTrue(result.equals("class rx.schedulers.Timestamped"));

6. Delay

Dieser Operator ändert seine QuelleObservable, indem er für einen bestimmten Zeitschritt pausiert, bevor er jedes Element der QuelleObservable’sausgibt.

Es versetzt die gesamte Sequenz mit dem angegebenen Wert:

Observable source = Observable.interval(1, TimeUnit.SECONDS)
  .take(5)
  .timestamp();

Observable delayedObservable
  = source.delay(2, TimeUnit.SECONDS);

source.subscribe(
  value -> System.out.println("source :" + value),
  t -> System.out.println("source error"),
  () -> System.out.println("source completed"));

delayedObservable.subscribe(
  value -> System.out.println("delay : " + value),
  t -> System.out.println("delay error"),
  () -> System.out.println("delay completed"));
Thread.sleep(8000);

Es gibt einen alternativen Operator, mit dem wir das Abonnement der Quelle Observable mit dem NamendelaySubscription verzögern können.

Der OperatorDelay wird standardmäßig aufcomputationScheduler ausgeführt. Wir können jedoch ein anderesScheduler auswählen, indem wir es als optionalen dritten Parameter andelaySubscription übergeben.

7. Repeat

Repeat fängt einfach die Abschlussbenachrichtigung vom Upstream ab und abonniert sie erneut, anstatt sie stromabwärts weiterzuleiten.

Daher kann nicht garantiert werden, dassrepeat weiterhin dieselbe Abfolge von Ereignissen durchläuft, aber es ist der Fall, wenn der Upstream ein fester Stream ist:

Observable.range(1, 3)
  .repeat(3)
  .subscribe(i -> receivedTotal += i);

assertTrue(receivedTotal == 18);

8. Cache

Der Operatorcache steht zwischensubscribe und unserem benutzerdefiniertenObservable.

Wenn der erste Abonnent angezeigt wird, delegiertcache das Abonnement an das zugrunde liegendeObservable und leitet alle Benachrichtigungen (Ereignisse, Abschlüsse oder Fehler) nachgelagert weiter.

Gleichzeitig wird jedoch eine Kopie aller Benachrichtigungen intern aufbewahrt. Wenn ein nachfolgender Abonnent Push-Benachrichtigungen erhalten möchte, delegiertcache nicht mehr an die zugrunde liegendenObservable, sondern füttert stattdessen zwischengespeicherte Werte:

Observable source =
  Observable.create(subscriber -> {
      System.out.println("Create");
      subscriber.onNext(receivedTotal += 5);
      subscriber.onCompleted();
  }).cache();
source.subscribe(i -> {
  System.out.println("element 1");
  receivedTotal += 1;
});
source.subscribe(i -> {
  System.out.println("element 2");
  receivedTotal += 2;
});

assertTrue(receivedTotal == 8);

9. Using

Wenn einobserver die vonusing() zurückgegebenenObservable abonniert, verwendet es die Factory-FunktionObservable, umObservable undobserver zu erstellen wird… beobachten, während gleichzeitig die Resource Factory-Funktion verwendet wird, um die Ressource zu erstellen, für die wir sie entworfen haben.

Wenn sichobserver vonObservable abmeldet oder wennObservable beendet wird, ruftusing die dritte Funktion auf, um die erstellte Ressource zu entsorgen:

Observable values = Observable.using(
  () -> "resource",
  r -> {
      return Observable.create(o -> {
          for (Character c : r.toCharArray()) {
              o.onNext(c);
          }
          o.onCompleted();
      });
  },
  r -> System.out.println("Disposed: " + r)
);
values.subscribe(
  v -> result += v,
  e -> result += e
);
assertTrue(result.equals("resource"));

10. Fazit

In diesem Artikel haben wir uns mit der Verwendung von RxJava-Dienstprogrammoperatoren und den wichtigsten Funktionen befasst.

Die wahre Kraft von RxJava liegt in seinen Operatoren. Deklarative Transformationen von Datenströmen sind sicher, aber aussagekräftig und flexibel.

Mit einer starken Grundlage in der funktionalen Programmierung spielen Operatoren eine entscheidende Rolle bei der Einführung von RxJava. Das Beherrschen von eingebauten Operatoren ist ein Schlüssel zum Erfolg in dieser Bibliothek.

Der vollständige Quellcode für das Projekt einschließlich aller hier verwendeten Codebeispiele befindet sich inover on GitHub.