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: