一、前言
今天,我們來整理以下幾個大家容易弄混的概念,並用實際例子來演示,可以從 RxSample 的第十二章中獲取:
publish
reply
ConnectableObservable
connect
share
refCount
autoConnect
對於以上這些概念,可以用一幅圖來概括:
從圖中可以看出,這裡面可以供使用者訂閱的Observable
可以分為四類,下面我們將逐一介紹這幾種Observable
的特點:
- 第一類:
Cold Observable
,就是我們通過Observable.create
、Observable.interval
等建立型操作符生成的Observable
。 - 第二類:由
Cold Observable
經過publish()
或者replay(int N)
操作符轉換成的ConnectableObservable
。 - 第三類:由
ConnectableObservable
經過refCount()
,或者由Cold Observable
經過share()
轉換成的Observable
。 - 第四類:由
ConnectableObservable
經過autoConnect(int N)
轉換成的Observable
。
二、Cold Observable
Cold Observable
就是我們通過Observable.create
、Observable.interval
等建立型操作符生成的Observable
,它具有以下幾個特點:
- 當一個訂閱者訂閱
Cold Observable
時,Cold Observable
會重新開始發射資料給該訂閱者。 - 當多個訂閱者訂閱到同一個
Cold Observable
,它們收到的資料是相互獨立的。 - 當一個訂閱者取消訂閱
Cold Observable
後,Cold Observable
會停止發射資料給該訂閱者,但不會停止發射資料給其它訂閱者。
下面,我們演示一個例子,首先我們建立一個Cold Observable
:
//直接訂閱Cold Observable。
private void createColdSource() {
mConvertObservable = getSource();
}
private Observable<Integer> getSource() {
return Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> observableEmitter) throws Exception {
try {
int i = 0;
while (true) {
Log.d(TAG, "源被訂閱者發射資料=" + i + ",傳送執行緒ID=" + Thread.currentThread().getId());
mSourceOut.add(i);
observableEmitter.onNext(i++);
updateMessage();
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).subscribeOn(Schedulers.io());
}
複製程式碼
在建立兩個訂閱者,它們可以隨時訂閱到Cold Observable
或者取消對它的訂閱:
private void startSubscribe1() {
if (mConvertObservable != null && mDisposable1 == null) {
mDisposable1 = mConvertObservable.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Log.d(TAG, "訂閱者1收到資料=" + integer + ",接收執行緒ID=" + Thread.currentThread().getId());
mSubscribe1In.add(integer);
updateMessage();
}
});
}
}
private void disposeSubscribe1() {
if (mDisposable1 != null) {
mDisposable1.dispose();
mDisposable1 = null;
mSubscribe1In.clear();
updateMessage();
}
}
private void startSubscribe2() {
if (mConvertObservable != null && mDisposable2 == null) {
mDisposable2 = mConvertObservable.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Log.d(TAG, "訂閱者2收到資料=" + integer + ",接收執行緒ID=" + Thread.currentThread().getId());
mSubscribe2In.add(integer);
updateMessage();
}
});
}
}
private void disposeSubscribe2() {
if (mDisposable2 != null) {
mDisposable2.dispose();
mDisposable2 = null;
mSubscribe2In.clear();
updateMessage();
}
}
複製程式碼
為了驗證之前說到的幾個特點,進入程式之後,我們會先建立該Cold Observable
,之後進行一系列的操作,效果如下:
- 第一步:啟動應用,建立
Cold Observable
,這時候Cold Observable
沒有傳送任何資料。 - 第二步:
Observer1
訂閱Observable
,此時Cold Observable
開始傳送資料,Observer1
也可以收到資料,即 一個訂閱者訂閱 Cold Observable 時, Cold Observable 會開始發射資料給該訂閱者 - 第三步:
Observer2
訂閱Observable
,此時Observable2
也可以收到資料,但是它和Observable1
收到的資料是相互獨立的,即 當多個訂閱者訂閱到同一個 Cold Observable ,它們收到的資料是相互獨立的。 - 第四步:
Observer1
取消對Observable
的訂閱,這時候Observer1
收不到資料,並且Observable
也不會發射資料給它,但是仍然會發射資料給Observer2
,即 當一個訂閱者取消訂閱 Cold Observable 後,Cold Observable 會停止發射資料給該訂閱者,但不會停止發射資料給其它訂閱者。 - 第五步:
Observer1
重新訂閱Observable
,這時候Observable
從0
開始發射資料給Observer1
,即 一個訂閱者訂閱 Cold Observable 時, Cold Observable 會重新開始發射資料給該訂閱者。
三、由 Cold Observable 轉換的 ConnectableObservable
在瞭解完Cold Observable
之後,我們再來看第二類的Observable
,它的型別為ConnectableObservable
,它是通過Cold Observable
經過下面兩種方式生成的:
.publish()
.reply(int N)
如果使用.publish()
建立,那麼訂閱者只能收到在訂閱之後Cold Observable
發出的資料,而如果使用reply(int N)
建立,那麼訂閱者在訂閱後可以收到Cold Observable
在訂閱之前傳送的N
個資料。
我們先以publish()
為例,介紹ConnectableObservable
的幾個特點:
- 無論
ConnectableObservable
有沒有訂閱者,只要呼叫了ConnectableObservable
的connect
方法,Cold Observable
就開始傳送資料。 connect
會返回一個Disposable
物件,呼叫了該物件的dispose
方法,Cold Observable
將會停止傳送資料,所有ConnectableObservable
的訂閱者也無法收到資料。- 在呼叫
connect
返回的Disposable
物件後,如果重新呼叫了connect
方法,那麼Cold Observable
會重新傳送資料。 - 當一個訂閱者訂閱到
ConnectableObservable
後,該訂閱者會收到在訂閱之後,Cold Observable
傳送給ConnectableObservable
的資料。 - 當多個訂閱者訂閱到同一個
ConnectableObservable
時,它們收到的資料是相同的。 - 當一個訂閱者取消對
ConnectableObservable
,不會影響其他訂閱者收到訊息。
下面,我們建立一個ConnectableObservable
,兩個訂閱者之後會訂閱到它,而不是Cold Observable
:
//.publish()將源Observable轉換成為HotObservable,當呼叫它的connect方法後,無論此時有沒有訂閱者,源Observable都開始傳送資料,訂閱者訂閱後將可以收到資料,並且訂閱者解除訂閱不會影響源Observable資料的發射。
public void createPublishSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish();
mConvertDisposable = ((ConnectableObservable<Integer>) mConvertObservable).connect();
}
複製程式碼
和上面一樣,還是用一個例子來演示,該例子的效果為:
- 第一步:啟動應用,通過
Cold Observable
的publish
方法建立ConnectableObservable
,並呼叫ConnectableObservable
的connect
方法,可以看到,此時雖然ConnectableObservable
沒有任何訂閱者,但是Cold Observable
也已經開始傳送資料。 - 第二步:
Observer1
訂閱到ConnectableObservable
,此時它只能收到訂閱之後Cold Observable
發射的資料。 - 第三步:
Observer2
訂閱到ConnectableObservable
,Cold Observable
只會發射一份資料,並且Observer1
和Observer2
收到的資料是相同的。 - 第三步:
Observer1
取消對ConnectableObservable
的訂閱,Cold Observable
仍然會發射資料,Observer2
仍然可以收到Cold Observable
發射的資料。 - 第四步:
Observer1
重新訂閱ConnectableObservable
,和第三步相同,它仍然只會收到訂閱之後Cold Observable
發射的資料。 - 第五步:通過
connect
返回的Disposable
物件,呼叫dispose
方法,此時Cold Observable
停止發射資料,並且Observer1
和Observer2
都收不到資料。
上面這些現象發生的根本原因在於:現在Observer
和Observer2
都是訂閱到ConnectableObservable
,真正產生資料的Cold Observable
並不知道他們的存在,和它互動的是ConnectableObservable
,ConnectableObservable
相當於一箇中介,它完成下面兩項任務:
- 對於上游:通過
connect
和dispose
方法決定是否要訂閱到Cold Observer
,也就是決定了Cold Observable
是否傳送資料。 - 對於下游:將
Cold Observable
傳送的資料轉交給它的訂閱者。
四、由 ConnectableObservable 轉換成 Observable
由ConnectableObservable
轉換成Observable
有兩種方法,我們分為兩節介紹下當訂閱到轉換後的Observable
時的現象:
.refCount()
.autoConnect(int N)
4.1 ConnectableObservable 由 refCount 轉換成 Observable
經過refCount
方法,ConnectableObservable
可以轉換成正常的Observable
,我們稱為refObservable
,這裡我們假設ConnectableObservable
是由Cold Observable
通過publish()
方法轉換的,對於它的訂閱者,有以下幾個特點:
- 第一個訂閱者訂閱到
refObservable
後,Cold Observable
開始傳送資料。 - 之後的訂閱者訂閱到
refObservable
後,只能收到在訂閱之後Cold Observable
傳送的資料。 - 如果一個訂閱者取消訂閱到
refObservable
後,假如它是當前refObservable
的唯一一個訂閱者,那麼Cold Observable
會停止傳送資料;否則,Cold Observable
仍然會繼續傳送資料,其它的訂閱者仍然可以收到Cold Observable
傳送的資料。
接著上例子,我們建立一個refObservable
:
//.share()相當於.publish().refCount(),當有訂閱者訂閱時,源訂閱者會開始傳送資料,如果所有的訂閱者都取消訂閱,源Observable就會停止傳送資料。
private void createShareSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish().refCount();
}
複製程式碼
示例如下:
操作分為以下幾步:- 第一步:通過
.publish().refCount()
建立由ConnectableObservable
轉換後的refObservable
,此時Cold Observable
沒有傳送任何訊息。 - 第二步:
Observer1
訂閱到refObservable
,Cold Observable
開始傳送資料,Observer1
接收資料。 - 第三步:
Observer2
訂閱到refObservable
,它只能收到在訂閱之後Cold Observable
傳送的資料。 - 第四步:
Observer1
取消訂閱,Cold Observable
繼續傳送資料,Observer2
仍然能收到資料。 - 第五步:
Observer2
取消訂閱,Cold Observable
停止傳送資料。 - 第六步:
Observer1
重新訂閱,Cold Observable
重新開始傳送資料。
最後說明一點:訂閱到Cold Observable
的.publish().refCount()
和Cold Observable
的share()
所返回的Observable
是等價的。
4.2 ConnectableObservable 由 autoConnect(int N) 轉換成 Observable
autoConnect(int N)
和refCount
很類似,都是將ConnectableObservable
轉換成普通的Observable
,我們稱為autoObservable
,同樣我們先假設ConnectableObservable
是由Cold Observable
通過publish()
方法生成的,它有以下幾個特點:
- 當有
N
個訂閱者訂閱到refObservable
後,Cold Observable
開始傳送資料。 - 之後的訂閱者訂閱到
refObservable
後,只能收到在訂閱之後Cold Observable
傳送的資料。 - 只要
Cold Observable
開始傳送資料,即使所有的autoObservable
的訂閱和都取消了訂閱,Cold Observable
也不會停止傳送資料,如果想要Cold Observable
停止傳送資料,那麼可以使用autoConnect(int numberOfSubscribers, Consumer connection)
中Consumer
返回的Disposable
,它的作用和ConnectableObservable
的connect
方法返回的Disposable
相同。
其建立方法如下所示:
//.autoConnect在有指定個訂閱者時開始讓源Observable傳送訊息,但是訂閱者是否取消訂閱不會影響到源Observable的發射。
private void createAutoConnectSource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.publish().autoConnect(1, new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
mConvertDisposable = disposable;
}
});
}
複製程式碼
示例效果如下:
我們進行了如下幾步操作:- 第一步:啟動應用,建立
autoConnect
轉換後的autoObservable
。 - 第二步:
Observer1
訂閱到autoObservable
,此時滿足條件,Cold Observable
開始傳送資料。 - 第三步:
Observer2
訂閱到autoObservable
,它只能收到訂閱後發生的資料。 - 第四步:
Observer1
取消訂閱,Cold Observable
繼續傳送資料,Observer2
仍然可以收到資料。 - 第五步:
Observer2
取消訂閱,Cold Observable
仍然繼續傳送資料。 - 第六步:
Observer2
訂閱到autoObservable
,它只能收到訂閱後傳送的訊息了。 - 第七步:呼叫
mConvertDisposable
的dispose
,Cold Observable
停止傳送資料。
五、publish 和 reply(int N) 的區別
在上面的例子當中,所有總結的特點都是建立在ConnectableObservable
是由publish()
生成,只所以這麼做,是為了方便大家理解,無論是訂閱到ConnectableObservable
,還是由ConnectableObservable
轉換的refObservable
和autoObservable
,使用這兩種方式建立的唯一區別就是,訂閱者在訂閱後,如果是通過publish()
建立的,那麼訂閱者之後收到訂閱後Cold Observable
傳送的資料;而如果是reply(int N)
建立的,那麼訂閱者還能額外收到N
個之前Cold Observable
傳送的資料,我們用下面一個小例子來演示,訂閱者訂閱到的Observable
如下:
//.reply會讓快取源Observable的N個資料項,當有新的訂閱者訂閱時,它會傳送這N個資料項給它。
private void createReplySource() {
mColdObservable = getSource();
mConvertObservable = mColdObservable.replay(3);
mConvertDisposable = ((ConnectableObservable<Integer>) mConvertObservable).connect();
}
複製程式碼
示例演示效果:
操作步驟:- 第一步:啟動應用,通過
Cold Observable
的publish
方法建立ConnectableObservable
,並呼叫ConnectableObservable
的replay(3)
方法,可以看到,此時雖然ConnectableObservable
沒有任何訂閱者,但是Cold Observable
也已經開始傳送資料。 - 第二步:
Observer1
訂閱到ConnectableObservable
,此時它會先收到之前發射的3
個資料,之後收到訂閱之後Cold Observable
發射的資料。
最後再提一下,更詳細的程式碼大家可以從 RxSample 的第十二章中獲取。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/