Operation details and examples of Rxjava2 connectable Observable

Keywords: Mobile Fragment github

Briefly:

Requirements understanding:

Ordinary Observable in Rxjava will transmit data when the observer subscribes, but sometimes we want to control the data transmission by ourselves. For example, we need to use the connectable Observable in Rxjava to fulfill this requirement after having a specified observer or all observers subscribing.

This section mainly introduces ConnectableObservable, its subclasses and their operators:

  • ConnectableObservable: a connectable Observable that does not transmit data after subscription. It starts to transmit data after calling the connect() method.
  • Observable.publish(): converts an observable to a connectable observable.
  • ConnectableObservable.connect(): indicates that a connectable Observable starts transmitting data.
  • Observable.replay(): ensure that all subscribers see the same sequence of data, even if they do not subscribe until observable starts to emit data.
  • ConnectableObservable.refCount(): make a connectable Observable behave like a normal Observable.
  • Observable.share(): you can directly convert observable into an observable object with ConnectableObservable property, which is equivalent to Observable.publish().refCount()
  • Observable.replay(): ensure that all observers receive the same data sequence, even if they subscribe after the observable starts to transmit data.

1. ConnectableObservable

A connectable Observable is similar to a normal Observable. Difference: connectable Observable does not start transmitting data when it is subscribed, only when its connect() is called. In this way, you can wait for some or all potential subscribers to subscribe to the Observable before launching data.

Note: thread switching of ConnectableObservable can only be realized by replay operator. subscribeOn() and observerOn() of ordinary Observable do not work in ConnectableObservable. You can switch threads by specifying the thread scheduler of the replay operator.

Javadoc: ConnectableObservable

2. Publish

Convert ordinary Observable to connectable Observable.

If you want to use a connectable Observable, you can use the publish operator of the Observable to convert it to a ConnectableObservable object.

There is a variation that takes a function as a parameter (publish(Function selector)). This function takes the data transmitted by the original Observable as a parameter, generates a new data as ConnectableObservable to transmit, and replaces the data item of the original location. The essence is to add a Map operation on the basis of signature.

Simple example:

  // 1. publish()
  // Create ConnectableObservable
  ConnectableObservable<Integer> connectableObservable = Observable.range(1, 5)
          .publish();    // The publish operation converts the Observable to a connectable Observable

	// 2. publish(Function<Observable<T>, ObservableSource<R>> selector)
  // Accept the data of the original Observable and generate a new Observable, which can be processed as a function
  Observable<String> publish = Observable.range(1, 5)
          .publish(new Function<Observable<Integer>, ObservableSource<String>>() {

              @Override
              public ObservableSource<String> apply(Observable<Integer> integerObservable) throws Exception {
                  System.out.println("--> apply(4): " + integerObservable.toString());

                  Observable<String> map = integerObservable.map(new Function<Integer, String>() {

                      @Override
                      public String apply(Integer integer) throws Exception {
                          return "[this is map value]: " + integer * integer;
                      }
                  });
                  return map;
              }
          });
          
    publish.subscribe(new Consumer<String>() {
        @Override
        public void accept(String s) throws Exception {
            System.out.println("--> accept(4): " + s);
        }
    });

Output:

--> apply(4): io.reactivex.subjects.PublishSubject@3fb4f649
--> accept(4): [this is map value]: 1
--> accept(4): [this is map value]: 4
--> accept(4): [this is map value]: 9
--> accept(4): [this is map value]: 16
--> accept(4): [this is map value]: 25

Javadoc: Observable.publish()
Javadoc: Observable.publish(Function<Observable<T>,ObservableSource<R> selector)

3. Connect

Let a connectable Observable start transmitting data to subscribers.

  • The connectable Observable (connectable Observable) is similar to the ordinary Observable, but it does not start transmitting data when it is subscribed, but does not start until the Connect operator is used.
  • In RxJava, connect is a method of the ConnectableObservable interface. Using the publish operator, you can convert a normal Observable to a ConnectableObservable.
  • Calling the ConnectableObservable's connect method causes the Observable after it to start sending data to subscribers. The connect method returns a Subscription object. You can call its unsubscribe method to stop the Observable from transmitting data to the observer.
  • Even if no subscriber subscribes to it, you can use the connect method to let an Observable start transmitting data (or start generating data to be transmitted). In this way, you can turn a "cold" Observable into a "hot" one.

Instance code:

    // 1. publish()
    // Create ConnectableObservable
    ConnectableObservable<Integer> connectableObservable = Observable.range(1, 5)
            .publish();    // The publish operation converts the Observable to a connectable Observable

    // Create a normal Observable
    Observable<Integer> range = Observable.range(1, 5);

    // 1.1 connectableObservable does not start transmitting data when it is subscribed, only when its connect() is called
    connectableObservable.subscribe(new Observer<Integer>() {

        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(1)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(1): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(1): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onComplete(1)");
        }
    });

    // 1.2 connectableObservable does not start transmitting data when it is subscribed, only when its connect() is called
    connectableObservable.subscribe(new Observer<Integer>() {

        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(2)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(2): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(2): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onComplete(2)");
        }
    });

    // 1.3 normal Observable will transmit data when it is subscribed
    range.subscribe(new Observer<Integer>() {

        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("--> onSubscribe(3)");
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("--> onNext(3): " + integer);
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(3): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onComplete(3)");
        }
    });

    System.out.println("----------------start connect------------------");
    // The connectable Observable does not start transmitting data when it is subscribed, only when its connect() is called
    // connectableObservable.connect();
    
    // The optional parameter Consumer returns a Disposable object, which can get the subscription status and cancel the current subscription
    connectableObservable.connect(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("--> connect accept: " + disposable.isDisposed());
            // disposable.dispose();
        }
    });

Output:

--> onSubscribe(1)
--> onSubscribe(2)
--> onSubscribe(3)
--> onNext(3): 1
--> onNext(3): 2
--> onNext(3): 3
--> onNext(3): 4
--> onNext(3): 5
--> onComplete(3)
----------------start connect------------------
--> connect accept: false
--> onNext(1): 1
--> onNext(2): 1
--> onNext(1): 2
--> onNext(2): 2
--> onNext(1): 3
--> onNext(2): 3
--> onNext(1): 4
--> onNext(2): 4
--> onNext(1): 5
--> onNext(2): 5
--> onComplete(1)
--> onComplete(2)

Javadoc: ConnectableObservable.connect()
Javadoc: ConnectableObservable.connect(Consumer<Disposable> connection)

4. RefCount

RefCount is used to make a connectable Observable behave like an ordinary Observable.

The RefCount operator automates the process of connecting and disconnecting from a connectable Observable. It operates on a connectable Observable and returns a normal Observable. When the first subscriber subscribes to this Observable, RefCount connects to the lower level of connectable Observable. RefCount tracks how many observers subscribe to it and does not disconnect from the underlying Observable until the last observer completes.

Resolution: refCount() changes ConnectableObservable into a normal Observable, but keeps the characteristics of ConnectableObservable. If the first Observer appears, it will automatically call connect(). If all the observers dispose, it will also stop accepting the data of the upstream Observable.

Instance code:

    /**
     * refCount(int subscriberCount, long timeout, TimeUnit unit, Scheduler scheduler)
     *
     * With the following optional parameters:
     * subscriberCount:  Specifies the number of subscribers that need to connect to the upstream. Note: it will be processed only when subscribers meet this number
     * timeout:          Waiting time for all subscribers to unsubscribe and disconnect
     * unit:             Time unit
     * scheduler:        Destination scheduler to wait before disconnecting
     */
    Observable<Long> refCountObservable = Observable
            .intervalRange(1, 5, 0, 1000, TimeUnit.MILLISECONDS)
            .publish()
            .refCount()
            .subscribeOn(Schedulers.newThread())    // Specify that the subscription is scheduled in the child thread
            .observeOn(Schedulers.newThread());     // Specifies that the watcher is scheduled on a child thread
        //  .refCount(1, 500, TimeUnit.MILLISECONDS, Schedulers.newThread());

    // 1st subscriber
    refCountObservable.subscribe(new Observer<Long>() {
        private  Disposable disposable;
        private  int buff = 0;

        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("----> onSubscribe(1): ");
            disposable = d;
        }

        @Override
        public void onNext(Long value) {
            if (buff == 3) {
                disposable.dispose();   // Unsubscribe from the current subscription
                System.out.println("----> Subscribe(1) is dispose! ");
            } else {
                System.out.println("--> onNext(1): " + value);
            }
            buff++;
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(1): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onComplete(1): ");
        }
    });

    // 2nd subscriber
    refCountObservable.doOnSubscribe(new Consumer<Disposable>() {

                @Override
                public void accept(Disposable disposable) throws Exception {
                    System.out.println("----> onSubscribe(2): ");
                }
            })
            .delaySubscription(2, TimeUnit.SECONDS)   // Subscription after 2 seconds delay
            .subscribe(new Consumer<Long>() {

                @Override
                public void accept(Long value) throws Exception {
                    System.out.println("--> accept(2): " + value);
                }
            });

    System.in.read();

Output:

----> onSubscribe(1): 
--> onNext(1): 1
--> onNext(1): 2
--> onNext(1): 3
----> onSubscribe(2): 
----> Subscribe(1) is dispose! 
--> accept(2): 4
--> accept(2): 5

Javadoc: ConnectableObservable.refCount(subscriberCount, timeout, unit, scheduler)

5. Share

A normal Observable can be converted to ConnectableObservable by publish ing, and then it can be converted to an Observable with ConnectableObservable property by calling its refCount() method.

In fact, there is another operation method in Observable, which can directly complete the operation of this step. This is the Observable.share() operator.

You can see the source code of the share operator:

    public final Observable<T> share() {
        return publish().refCount();
    }

We can know from the source code that the share () method can directly convert the Observable into an Observable object with ConnectableObservable property, that is, Observable.publish().refCount() == Observable.share().

Instance code:

    // share()
    // Apply publish and refCount operations simultaneously through share()
    Observable<Long> share = Observable
            .intervalRange(1, 5, 0, 500, TimeUnit.MILLISECONDS)
      //    .publish().refCount()
            .share()  // Equivalent to the above operation
            .subscribeOn(Schedulers.newThread())    // Specify that the subscription is scheduled in the child thread
            .observeOn(Schedulers.newThread());     // Specifies that the watcher is scheduled on a child thread

    // 1. First subscriber
    share.subscribe(new Observer<Long>() {
        private  Disposable disposable;
        private  int buff = 0;

        @Override
        public void onSubscribe(Disposable d) {
            System.out.println("----> onSubscribe(1): ");
            disposable = d;
        }

        @Override
        public void onNext(Long value) {
            if (buff == 3) {
                disposable.dispose();   // Unsubscribe from the current subscription
                System.out.println("----> Subscribe(1) is dispose! ");
            } else {
                System.out.println("--> onNext(1): " + value);
            }
            buff++;
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("--> onError(1): " + e);
        }

        @Override
        public void onComplete() {
            System.out.println("--> onComplete(1): ");
        }
    });

    // 2. Second subscriber
    share.doOnSubscribe(new Consumer<Disposable>() {

                @Override
                public void accept(Disposable disposable) throws Exception {
                    System.out.println("----> onSubscribe(2): ");
                }
            })
            .delaySubscription(1, TimeUnit.SECONDS)    // Subscription after 1 second delay
            .subscribe(new Consumer<Long>() {

                @Override
                public void accept(Long value) throws Exception {
                    System.out.println("--> accept(2): " + value);
                }
            });

    System.in.read();

Output:

----> onSubscribe(1): 
--> onNext(1): 1
--> onNext(1): 2
--> onNext(1): 3
----> onSubscribe(2): 
----> Subscribe(1) is dispose! 
--> accept(2): 4
--> accept(2): 5

Javadoc: Observable.share()

6. Replay

Ensure that all observers receive the same data sequence, even if they subscribe after the Observable starts to transmit data.

If the Replay operator is used before converting an Observable to a connectable Observable, the generated connectable Observable will always transmit the complete data sequence to any future observer, and can cache the transmitted data, even if those observers subscribe after the Observable starts to transmit data to other observers.

Note: the connectableObservable generated by the replay operator can receive the complete data sequence item of the Observable no matter when the observer subscribes if the cache is not limited.

The replay operator is better to limit the size of the cache according to the actual situation, otherwise it will take up a lot of memory when the data is transmitted too fast or too much. Replay operators have variations that can accept different parameters. Some can specify the maximum number of replays to cache or the cache time, as well as the scheduler.

  • replay can not only cache all data sequences of Observable, but also limit the cache size.
  • There is also a replay that returns a normal Observable. It can accept a transformation function as a parameter, which takes the data item emitted by the original Observable as a parameter, and returns a data item to be emitted by the result Observable. Therefore, this operator is actually the data item after replay transformation.

Instance code:

    // Create an Observable to transmit data
    Observable<Long> observable = Observable
            .intervalRange(1,
                    10,
                    1,
                    500,
                    TimeUnit.MILLISECONDS,
                    Schedulers.newThread());

    /**
     * 1.1 replay(Scheduler scheduler)
     * Optional parameters: scheduler, specify thread scheduler
     * Accept all data from original data
     */
//  ConnectableObservable<Long> replay1 = observable.replay();

    /**
     * 1.2 replay(int bufferSize, Scheduler scheduler)
     * Optional parameters: scheduler, specify thread scheduler
     * Cache only bufferSize recent raw data
     */
//  Connectableobservable < long > replay1 = observable.replay (1); / / set the cache size to 1, and cache the latest data from the original data

    /**
     * 1.3 replay(int bufferSize, long time, TimeUnit unit, Scheduler scheduler)
     * Optional parameters: scheduler, specify thread scheduler
     * Cache bufferSize data in the specified time period before subscription. Note that timing starts after the first data item of the original data is sent
     */
//  ConnectableObservable<Long> replay1 = observable.replay(5, 1000, TimeUnit.MILLISECONDS);

    /**
     * 1.4 replay(long time, TimeUnit unit, Scheduler scheduler)
     * Optional parameters: scheduler, specify thread scheduler
     * Cache data within the specified time period before subscription. Note that timing starts after the first data item of the original data is transmitted
     */
   ConnectableObservable<Long> replay1 = observable.replay( 1000, TimeUnit.MILLISECONDS);

    // connect operation
    replay1.connect();

    // First observer
    replay1.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> onSubScribe(1-1)");
        }
    }).subscribe(new Consumer<Long>() {
        @Override
        public void accept(Long aLong) throws Exception {
            System.out.println("--> accept(1-1): " + aLong);
        }
    });

    // Second observer (subscription after 1 second delay)
    replay1.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> onSubScribe(1-2)");
        }
    }).delaySubscription(1, TimeUnit.SECONDS)
      .subscribe(new Consumer<Long>() {
            @Override
            public void accept(Long aLong) throws Exception {
                System.out.println("--> accept(1-2): " + aLong);
            }
      });

    // Third observer (subscription after 2 seconds delay)
    replay1.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("----> onSubScribe(1-3)");
        }
    }).delaySubscription(2, TimeUnit.SECONDS)
       .subscribe(new Consumer<Long>() {
           @Override
           public void accept(Long aLong) throws Exception {
               System.out.println("--> accept(1-3): " + aLong);
           }
       });

    System.in.read();
    System.out.println("----------------------------------------------------------");
    /**
     * 2. replay(Function<Observable<T>, ObservableSource<R>> selector,
     * int bufferSize,             				 	Optional parameter: Specifies the cache size of sequence data from metadata
     * long time, TimeUnit unit,    	Optional parameters: specifies to cache data series for a specified time period
     * Scheduler scheduler)         		Optional parameters: specify thread scheduler
     *
     * Take a transformation function as a parameter, which takes the data item emitted by the original Observable as a parameter
     * After being processed by the specified function, a processed Observable is returned
     */
    Observable<String> replayObservable = observable.replay(new Function<Observable<Long>, ObservableSource<String>>() {
        @Override
        public ObservableSource<String> apply(Observable<Long> longObservable) throws Exception {
            // Processing of raw data
            Observable<String> map = longObservable.map(new Function<Long, String>() {
                @Override
                public String apply(Long aLong) throws Exception {
                    return aLong + "² = " + aLong * aLong;  // Square the original data and convert it to string data type
                }
            });

            return map;
        }
    }, 1, Schedulers.newThread());

    replayObservable.subscribeOn(Schedulers.newThread())
            .observeOn(Schedulers.newThread());

    // First observer
    replayObservable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("--> onSubScribe(2-1)");
        }
    }).subscribe(new Consumer<String>() {
        @Override
        public void accept(String s) throws Exception {
            System.out.println("--> accept(2-1):  " + s);
        }
    });

    // Subscribe to the second observer (after 2 seconds delay)
    replayObservable.doOnSubscribe(new Consumer<Disposable>() {
        @Override
        public void accept(Disposable disposable) throws Exception {
            System.out.println("--> onSubScribe(2-2)");
        }
    }).delaySubscription(2, TimeUnit.SECONDS)
      .subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                System.out.println("--> accept(2-2): " + s);
            }
       });

    System.in.read();

Output:

----> onSubScribe(1-1)
--> accept(1-1): 1
--> accept(1-1): 2
--> accept(1-1): 3
----> onSubScribe(1-2)
--> accept(1-2): 2
--> accept(1-2): 3
--> accept(1-1): 4
--> accept(1-2): 4
--> accept(1-1): 5
--> accept(1-2): 5
----> onSubScribe(1-3)
--> accept(1-3): 4
--> accept(1-3): 5
--> accept(1-1): 6
--> accept(1-2): 6
--> accept(1-3): 6
--> accept(1-1): 7
--> accept(1-2): 7
--> accept(1-3): 7
--> accept(1-1): 8
--> accept(1-2): 8
--> accept(1-3): 8
--> accept(1-1): 9
--> accept(1-2): 9
--> accept(1-3): 9
--> accept(1-1): 10
--> accept(1-2): 10
--> accept(1-3): 10
----------------------------------------------------------
--> onSubScribe(2-1)
--> accept(2-1):  1² = 1
--> accept(2-1):  2² = 4
--> accept(2-1):  3² = 9
--> accept(2-1):  4² = 16
--> onSubScribe(2-2)
--> accept(2-1):  5² = 25
--> accept(2-2): 1² = 1
--> accept(2-2): 2² = 4
--> accept(2-1):  6² = 36
--> accept(2-2): 3² = 9
--> accept(2-1):  7² = 49
--> accept(2-1):  8² = 64
--> accept(2-2): 4² = 16
--> accept(2-2): 5² = 25
--> accept(2-1):  9² = 81
--> accept(2-2): 6² = 36
--> accept(2-1):  10² = 100
--> accept(2-2): 7² = 49
--> accept(2-2): 8² = 64
--> accept(2-2): 9² = 81
--> accept(2-2): 10² = 100

Javadoc: Observable.replay(int bufferSize, long time, TimeUnit unit, Scheduler scheduler)
Javadoc: Observable.replay(Function<Observable<T>,ObservableSource<R>> selector, int bufferSize, long time, TimeUnit unit, Scheduler scheduler)

Summary

The main core of Rxjava's connection operator is the concept of ConnectableObservable, a connectable Observable object. The connectable Observable does not directly emit data when it is subscribed, only when its connect() method is called. It is convenient for better control of data transmission behavior, and also has good operation ability for data. It can cache data, specify cache size, time fragment cache, etc.

Tip: Rxjava2 version used above: 2.2.12

Rx introduction and explanation and complete catalog reference: Rxjava2 introduction and detailed examples

Instance code:

Posted by hmb3801 on Tue, 31 Dec 2019 13:41:34 -0800