Введение в RxRelay для RxJava

Введение в RxRelay для RxJava

1. Вступление

Популярность RxJava привела к созданию множества сторонних библиотек, которые расширяют его функциональные возможности.

Многие из этих библиотек были ответом на типичные проблемы, с которыми сталкивались разработчики при использовании RxJava. RxRelay - одно из таких решений.

2. Работа сSubject

Проще говоря,Subject действует как мост междуObservable иObserver.. Поскольку этоObserver, он может подписаться на один или несколькоObservables и получать события от их.

Кроме того, учитывая, что это одновременноObservable, он может повторно отправлять события или отправлять новые события своим подписчикам. Более подробную информацию оSubject можно найти в этомarticle.

Одна из проблем сSubject заключается в том, что после полученияonComplete() илиonError() он больше не может перемещать данные. Иногда это желаемое поведение, а иногда - нет.

В случаях, когда такое поведение нежелательно, следует рассмотреть возможность использованияRxRelay.

3. Relayс

Relay - это в основномSubject, но без возможности вызыватьonComplete() иonError(),, поэтому он может постоянно передавать данные.

Это позволяет нам создавать мосты между различными типами API, не беспокоясь о случайном срабатывании состояния терминала.

Чтобы использоватьRxRelay, нам нужно добавить в наш проект следующую зависимость:


  com.jakewharton.rxrelay2
  rxrelay
  1.2.0

4. ТипыRelay

В библиотеке доступны три разных типаRelay. Здесь мы быстро рассмотрим все три.

4.1. PublishRelayс

Этот типRelay будет повторно отправлять все события после того, какObserver подписывается на него.

События будут разосланы всем подписчикам:

public void whenObserverSubscribedToPublishRelay_itReceivesEmittedEvents() {
    PublishRelay publishRelay = PublishRelay.create();
    TestObserver firstObserver = TestObserver.create();
    TestObserver secondObserver = TestObserver.create();

    publishRelay.subscribe(firstObserver);
    firstObserver.assertSubscribed();
    publishRelay.accept(5);
    publishRelay.accept(10);
    publishRelay.subscribe(secondObserver);
    secondObserver.assertSubscribed();
    publishRelay.accept(15);
    firstObserver.assertValues(5, 10, 15);

    // second receives only the last event
    secondObserver.assertValue(15);
}

В этом случае нет буферизации событий, поэтому это поведение аналогично холодномуObservable.

4.2. BehaviorRelayс

Этот типRelay будет повторно отправлять самое последнее наблюдаемое событие и все последующие события после того, как Observer подписался:

public void whenObserverSubscribedToBehaviorRelay_itReceivesEmittedEvents() {
    BehaviorRelay behaviorRelay = BehaviorRelay.create();
    TestObserver firstObserver = TestObserver.create();
    TestObserver secondObserver = TestObserver.create();
    behaviorRelay.accept(5);
    behaviorRelay.subscribe(firstObserver);
    behaviorRelay.accept(10);
    behaviorRelay.subscribe(secondObserver);
    behaviorRelay.accept(15);
    firstObserver.assertValues(5, 10, 15);
    secondObserver.assertValues(10, 15);
}

Когда мы создаемBehaviorRelay, мы можем указать значение по умолчанию, которое будет генерироваться, если нет других событий для генерации.

Чтобы указать значение по умолчанию, мы можем использовать методcreateDefault():

public void whenObserverSubscribedToBehaviorRelay_itReceivesDefaultValue() {
    BehaviorRelay behaviorRelay = BehaviorRelay.createDefault(1);
    TestObserver firstObserver = new TestObserver<>();
    behaviorRelay.subscribe(firstObserver);
    firstObserver.assertValue(1);
}

Если мы не хотим указывать значение по умолчанию, мы можем использовать методcreate():

public void whenObserverSubscribedToBehaviorRelayWithoutDefaultValue_itIsEmpty() {
    BehaviorRelay behaviorRelay = BehaviorRelay.create();
    TestObserver firstObserver = new TestObserver<>();
    behaviorRelay.subscribe(firstObserver);
    firstObserver.assertEmpty();
}

4.3. ReplayRelayс

Этот типRelay буферизует все полученные события и затем повторно отправляет их всем подписчикам, которые на него подписываются:

 public void whenObserverSubscribedToReplayRelay_itReceivesEmittedEvents() {
    ReplayRelay replayRelay = ReplayRelay.create();
    TestObserver firstObserver = TestObserver.create();
    TestObserver secondObserver = TestObserver.create();
    replayRelay.subscribe(firstObserver);
    replayRelay.accept(5);
    replayRelay.accept(10);
    replayRelay.accept(15);
    replayRelay.subscribe(secondObserver);
    firstObserver.assertValues(5, 10, 15);
    secondObserver.assertValues(5, 10, 15);
}

Все элементы помещаются в буфер, и все подписчики будут получать одинаковые события, поэтомуthis behavior is similar to the cold Observable.

Когда мы создаемReplayRelay, мы можем предоставить максимальный размер буфера и время жизни для событий.

Чтобы создатьRelay с ограниченным размером буфера, мы можем использовать методcreateWithSize(). Когда количество событий для буферизации превышает установленный размер буфера, предыдущие элементы будут отброшены:

public void whenObserverSubscribedToReplayRelayWithLimitedSize_itReceivesEmittedEvents() {
    ReplayRelay replayRelay = ReplayRelay.createWithSize(2);
    TestObserver firstObserver = TestObserver.create();
    replayRelay.accept(5);
    replayRelay.accept(10);
    replayRelay.accept(15);
    replayRelay.accept(20);
    replayRelay.subscribe(firstObserver);
    firstObserver.assertValues(15, 20);
}

Мы также можем создатьReplayRelay с максимальным временем ожидания для буферизованных событий, используя методcreateWithTime():

public void whenObserverSubscribedToReplayRelayWithMaxAge_thenItReceivesEmittedEvents() {
    SingleScheduler scheduler = new SingleScheduler();
    ReplayRelay replayRelay =
      ReplayRelay.createWithTime(2000, TimeUnit.MILLISECONDS, scheduler);
    long current =  scheduler.now(TimeUnit.MILLISECONDS);
    TestObserver firstObserver = TestObserver.create();
    replayRelay.accept(5);
    replayRelay.accept(10);
    replayRelay.accept(15);
    replayRelay.accept(20);
    Thread.sleep(3000);
    replayRelay.subscribe(firstObserver);
    firstObserver.assertEmpty();
}

5. ПользовательскийRelay

Все типы, описанные выше, расширяют общий абстрактный классRelay,, что дает нам возможность написать наш собственный классRelay.

Чтобы создать собственныйRelay, нам нужно реализовать три метода:accept(), hasObservers() иsubscribeActual().

Давайте напишем простой Relay, который будет повторно отправлять событие одному из случайно выбранных подписчиков:

public class RandomRelay extends Relay {
    Random random = new Random();

    List> observers = new ArrayList<>();

    @Override
    public void accept(Integer integer) {
        int observerIndex = random.nextInt() % observers.size();
        observers.get(observerIndex).onNext(integer);
    }

    @Override
    public boolean hasObservers() {
        return observers.isEmpty();
    }

    @Override
    protected void subscribeActual(Observer observer) {
        observers.add(observer);
        observer.onSubscribe(Disposables.fromRunnable(
          () -> System.out.println("Disposed")));
    }
}

Теперь мы можем проверить, что только один подписчик получит событие:

public void whenTwoObserversSubscribedToRandomRelay_thenOnlyOneReceivesEvent() {
    RandomRelay randomRelay = new RandomRelay();
    TestObserver firstObserver = TestObserver.create();
    TestObserver secondObserver = TestObserver.create();
    randomRelay.subscribe(firstObserver);
    randomRelay.subscribe(secondObserver);
    randomRelay.accept(5);
    if(firstObserver.values().isEmpty()) {
        secondObserver.assertValue(5);
    } else {
        firstObserver.assertValue(5);
        secondObserver.assertEmpty();
    }
}

6. Заключение

В этом руководстве мы рассмотрели RxRelay, тип, похожий наSubject, но без возможности запускать конечное состояние.

Более подробную информацию можно найти вdocumentation. И, как всегда, можно найти все образцы кодаover on GitHub.