RxJava2 實戰知識梳理(12) 實戰講解 publish & replay & share & refCount & autoCo

澤毛發表於2017-12-21

一、前言

今天,我們來整理以下幾個大家容易弄混的概念,並用實際例子來演示,可以從 RxSample 的第十二章中獲取:

  • publish
  • reply
  • ConnectableObservable
  • connect
  • share
  • refCount
  • autoConnect

對於以上這些概念,可以用一幅圖來概括:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo
從圖中可以看出,這裡面可以供使用者訂閱的Observable可以分為四類,下面我們將逐一介紹這幾種Observable的特點:

  • 第一類:Cold Observable,就是我們通過Observable.createObservable.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.createObservable.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,之後進行一系列的操作,效果如下:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo
在上面的圖中,我們做了一下幾步操作:

  • 第一步:啟動應用,建立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,這時候Observable0開始發射資料給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有沒有訂閱者,只要呼叫了ConnectableObservableconnect方法,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();
    }
複製程式碼

和上面一樣,還是用一個例子來演示,該例子的效果為:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo

  • 第一步:啟動應用,通過Cold Observablepublish方法建立ConnectableObservable,並呼叫ConnectableObservableconnect方法,可以看到,此時雖然ConnectableObservable沒有任何訂閱者,但是Cold Observable也已經開始傳送資料。
  • 第二步:Observer1訂閱到ConnectableObservable,此時它只能收到訂閱之後Cold Observable發射的資料。
  • 第三步:Observer2訂閱到ConnectableObservableCold Observable只會發射一份資料,並且Observer1Observer2收到的資料是相同的。
  • 第三步:Observer1取消對ConnectableObservable的訂閱,Cold Observable仍然會發射資料,Observer2仍然可以收到Cold Observable發射的資料。
  • 第四步:Observer1重新訂閱ConnectableObservable,和第三步相同,它仍然只會收到訂閱之後Cold Observable發射的資料。
  • 第五步:通過connect返回的Disposable物件,呼叫dispose方法,此時Cold Observable停止發射資料,並且Observer1Observer2都收不到資料。

上面這些現象發生的根本原因在於:現在ObserverObserver2都是訂閱到ConnectableObservable,真正產生資料的Cold Observable並不知道他們的存在,和它互動的是ConnectableObservableConnectableObservable相當於一箇中介,它完成下面兩項任務:

  • 對於上游:通過connectdispose方法決定是否要訂閱到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();
    }
複製程式碼

示例如下:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo
操作分為以下幾步:

  • 第一步:通過.publish().refCount()建立由ConnectableObservable轉換後的refObservable,此時Cold Observable沒有傳送任何訊息。
  • 第二步:Observer1訂閱到refObservableCold Observable開始傳送資料,Observer1接收資料。
  • 第三步:Observer2訂閱到refObservable,它只能收到在訂閱之後Cold Observable傳送的資料。
  • 第四步:Observer1取消訂閱,Cold Observable繼續傳送資料,Observer2仍然能收到資料。
  • 第五步:Observer2取消訂閱,Cold Observable停止傳送資料。
  • 第六步:Observer1重新訂閱,Cold Observable重新開始傳送資料。

最後說明一點:訂閱到Cold Observable.publish().refCount()Cold Observableshare()所返回的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,它的作用和ConnectableObservableconnect方法返回的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;
            }
        });
    }
複製程式碼

示例效果如下:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo
我們進行了如下幾步操作:

  • 第一步:啟動應用,建立autoConnect轉換後的autoObservable
  • 第二步:Observer1訂閱到autoObservable,此時滿足條件,Cold Observable開始傳送資料。
  • 第三步:Observer2訂閱到autoObservable,它只能收到訂閱後發生的資料。
  • 第四步:Observer1取消訂閱,Cold Observable繼續傳送資料,Observer2仍然可以收到資料。
  • 第五步:Observer2取消訂閱,Cold Observable仍然繼續傳送資料。
  • 第六步:Observer2訂閱到autoObservable,它只能收到訂閱後傳送的訊息了。
  • 第七步:呼叫mConvertDisposabledisposeCold Observable停止傳送資料。

五、publish 和 reply(int N) 的區別

在上面的例子當中,所有總結的特點都是建立在ConnectableObservable是由publish()生成,只所以這麼做,是為了方便大家理解,無論是訂閱到ConnectableObservable,還是由ConnectableObservable轉換的refObservableautoObservable,使用這兩種方式建立的唯一區別就是,訂閱者在訂閱後,如果是通過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();
    }
複製程式碼

示例演示效果:

RxJava2 實戰知識梳理(12)   實戰講解 publish & replay & share & refCount & autoCo
操作步驟:

  • 第一步:啟動應用,通過Cold Observablepublish方法建立ConnectableObservable,並呼叫ConnectableObservablereplay(3)方法,可以看到,此時雖然ConnectableObservable沒有任何訂閱者,但是Cold Observable也已經開始傳送資料。
  • 第二步:Observer1訂閱到ConnectableObservable,此時它會先收到之前發射的3個資料,之後收到訂閱之後Cold Observable發射的資料。

最後再提一下,更詳細的程式碼大家可以從 RxSample 的第十二章中獲取。


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章