Rxjava一些知識點學習

聲明:本文爲 RxJava2 實戰知識梳理的心得筆記,非原創,生澀的部分建議看原文。

1.線程切換釋疑

  • Schedulers.computation():用於計算任務,默認線程數等於處理器的數量。
  • Schedulers.from(Executor executor):使用Executor作爲調度器,我沒用過。
  • Schedulers.io( ):用於IO密集型任務,例如訪問網絡、數據庫操作等,也是我們最常使用的。
  • Schedulers.newThread( ):爲每一個任務創建一個新的線程。
  • Schedulers.trampoline( ):當其它排隊的任務完成後,在當前線程排隊開始執行。
  • Schedulers.single():所有任務共用一個後臺線程。

2.debounce、filter與switchMap做一個實時搜索

  • 使用debounce操作符,當輸入框發生變化時,不會立刻將事件發送給下游,而是等待200ms,如果在這段事件內,輸入框沒有發生變化,那麼才發送該事件;反之,則在收到新的關鍵詞後,繼續等待200ms。
    debounce原理類似於我們在收到請求之後,發送一個延時消息給下游,如果在這段延時時間內沒有收到新的請求,那麼下游就會收到該消息;而如果在這段延時時間內收到來新的請求,那麼就會取消之前的消息,並重新發送一個新的延時消息,以此類推。
    而如果在這段時間內,上游發送了onComplete消息,那麼即使沒有到達需要等待的時間,下游也會立刻收到該消息。
  • 使用filter操作符,只有關鍵詞的長度大於0時才發送事件給下游。
  • 使用switchMap操作符,這樣當發起了abc的請求之後,即使ab的結果返回了,也不會發送給下游,從而避免了出現前面介紹的搜索詞和聯想結果不匹配的問題。switchMap的原理是將上游的事件轉換成一個或多個新的Observable,但是有一點很重要,就是如果在該節點收到一個新的事件之後,那麼如果之前收到的時間所產生的Observable還沒有發送事件給下游,那麼下游就再也不會收到它發送的事件了。
 mPublishSubject.debounce(200, TimeUnit.MILLISECONDS).filter(new Predicate<String>() {

            @Override
            public boolean test(String s) throws Exception {
                return s.length() > 0;
            }

        }).switchMap(new Function<String, ObservableSource<String>>() {

            @Override
            public ObservableSource<String> apply(String query) throws Exception {
                return getSearchObservable(query);
            }

        }).observeOn(AndroidSchedulers.mainThread()).subscribe(mDisposableObserver);

    private Observable<String> getSearchObservable(final String query) {
        return Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
                Log.d("SearchActivity", "開始請求,關鍵詞爲:" + query);
                try {
                    Thread.sleep(100 + (long) (Math.random() * 500));
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
                Log.d("SearchActivity", "結束請求,關鍵詞爲:" + query);
                observableEmitter.onNext("完成搜索,關鍵詞爲:" + query);
                observableEmitter.onComplete();
            }
        }).subscribeOn(Schedulers.io());
    }

3.zip操作符

它接收多個Observable,以及一個函數,該函數的形參爲這些Observable發送的數據,並且要等所有的Observable都發射完會後纔會回調該函數。
通過zip操作符,我們就可以實現等待多個網絡請求完成再返回的需求。

4.輪詢操作

• 固定時延:使用intervalRange操作符,每間隔3s執行一次任務。
• 變長時延:使用repeatWhen操作符實現,第一次執行完任務後,等待4s再執行第二次任務,在第二次任務執行完成後,等待5s,依次遞增。

 private void startSimplePolling() {
        Observable<Long> observable = Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS).take(5)
.doOnNext(new Consumer<Long>() {

            @Override
            public void accept(Long aLong) throws Exception {
      //這裏使用了doOnNext,因此DisposableObserver的onNext要等到該方法執行完纔會回調。
                doWork(); 
            }

        });
        DisposableObserver<Long> disposableObserver = getDisposableObserver();
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    private void startAdvancePolling() {
        Log.d(TAG, "startAdvancePolling click");
        Observable<Long> observable = Observable.just(0L).doOnComplete(new Action() {

            @Override
            public void run() throws Exception {
                doWork();
            }

        }).repeatWhen(new Function<Observable<Object>, ObservableSource<Long>>() {

            private long mRepeatCount;

            @Override
            public ObservableSource<Long> apply(Observable<Object> objectObservable) throws Exception {
                //必須作出反應,這裏是通過flatMap操作符。
                return objectObservable.flatMap(new Function<Object, ObservableSource<Long>>() {

                    @Override
                    public ObservableSource<Long> apply(Object o) throws Exception {
                        if (++mRepeatCount > 4) {
                            //return Observable.empty(); //發送onComplete消息,無法觸發下游的onComplete回調。
                            return Observable.error(new Throwable("Polling work finished")); //發送onError消息,可以觸發下游的onError回調。
                        }
                        Log.d(TAG, "startAdvancePolling apply");
                        return Observable.timer(3000 + mRepeatCount * 1000, TimeUnit.MILLISECONDS);
                    }

                });
            }

        });
        DisposableObserver<Long> disposableObserver = getDisposableObserver();
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    private DisposableObserver<Long> getDisposableObserver() {

        return new DisposableObserver<Long>() {

            @Override
            public void onNext(Long aLong) {}

            @Override
            public void onError(Throwable throwable) {
                Log.d(TAG, "DisposableObserver onError, threadId=" + Thread.currentThread().getId() + ",reason=" + throwable.getMessage());
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "DisposableObserver onComplete, threadId=" + Thread.currentThread().getId());
            }
        };
    }

    private void doWork() {
        long workTime = (long) (Math.random() * 500) + 500;
        try {
            Log.d(TAG, "doWork start,  threadId=" + Thread.currentThread().getId());
            Thread.sleep(workTime);
            Log.d(TAG, "doWork finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

4.1 intervalRange & doOnNext 實現固定時延輪詢

該操作符的優勢在於:
• 與interval相比,它可以指定第一個發送數據項的時延、指定發送數據項的個數。
• 與range相比,它可以指定兩項數據之間發送的時延。
intervalRange的接收參數的含義爲:
// Observable.intervalRange(0, 5, 0, 3000, TimeUnit.MILLISECONDS)
• start:發送數據的起始值,爲Long型。
• count:總共發送多少項數據。
• initialDelay:發送第一個數據項時的起始時延。
• period:兩項數據之間的間隔時間。
• TimeUnit:時間單位。
在輪詢操作中一般會進行一些耗時的網絡請求,因此我們選擇在doOnNext進行處理,它會在下游的onNext方法被回調之前調用,但是它的運行線程可以通過subscribeOn指定,下游的運行線程再通過observerOn切換會主線程,通過打印對應的線程ID可以驗證結果。
當要求的數據項都發送完畢之後,最後會回調onComplete方法。

4.2 repeatWhen 實現變長時延輪詢

repeatWhen來實現輪詢,是因爲它爲我們提供了重訂閱的功能,而重訂閱有兩點要素:
• 上游告訴我們一次訂閱已經完成,這就需要上游回調onComplete函數。
• 我們告訴上游是否需要重訂閱,通過repeatWhen的Function函數所返回的Observable確定,如果該Observable發送了onComplete或者onError則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。

repeatWhen的難點在於如何定義它的Function參數:
1.如果輸出的Observable發送了onComplete或者onError則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。也就是說,它 僅僅是作爲一個是否要觸發重訂閱的通知,onNext發送的是什麼數據並不重要。
2.對於每一次訂閱的數據流 Function 函數只會回調一次,並且是在onComplete的時候觸發,它不會收到任何的onNext事件。
而當我們不需要重訂閱時,有兩種方式:
• 返回Observable.empty(),發送onComplete消息,但是DisposableObserver並不會回調onComplete。
• 返回Observable.error(new Throwable(“Polling work finished”)),DisposableObserver的onError會被回調,並接受傳過去的錯誤信息。

 private static final String[] MSG_ARRAY = new String[] {
            MSG_WAIT_SHORT,
            MSG_WAIT_SHORT,
            MSG_WAIT_LONG,
            MSG_WAIT_LONG
    };
 private void startRetryRequest() {
        Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                int msgLen = MSG_ARRAY.length;
                doWork();
                //模擬請求的結果,前四次都返回失敗,並將失敗信息遞交給retryWhen。
                if (mMsgIndex < msgLen) { //模擬請求失敗的情況。
                    e.onError(new Throwable(MSG_ARRAY[mMsgIndex]));
                    mMsgIndex++;
                } else { //模擬請求成功的情況。
                    e.onNext("Work Success");
                    e.onComplete();
                }
            }

        }).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

            private int mRetryCount;

            @Override
            public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
                return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {

                    @Override
                    public ObservableSource<?> apply(Throwable throwable) throws Exception {
                        String errorMsg = throwable.getMessage();
                        long waitTime = 0;
                        switch (errorMsg) {
                            case MSG_WAIT_SHORT:
                                waitTime = 2000;
                                break;
                            case MSG_WAIT_LONG:
                                waitTime = 4000;
                                break;
                            default:
                                break;
                        }
                        Log.d(TAG, "發生錯誤,嘗試等待時間=" + waitTime + ",當前重試次數=" + mRetryCount);
                        mRetryCount++;
                        return waitTime > 0 && mRetryCount <= 4 ? 
                        Observable.timer(waitTime, TimeUnit.MILLISECONDS) : Observable.error(throwable);
                    }

                });
            }

        });
        DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {

            @Override
            public void onNext(String value) {
                Log.d(TAG, "DisposableObserver onNext=" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "DisposableObserver onError=" + e);
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "DisposableObserver onComplete");
            }
        };
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

retryWhen提供了 重訂閱 的功能,對於retryWhen來說,它的重訂閱觸發有兩點要素:

  • 上游通知retryWhen本次訂閱流已經完成,詢問其是否需要重訂閱,該詢問是以onError事件觸發的。
  • retryWhen根據onError的類型,決定是否需要重訂閱,它通過返回一個ObservableSource來通知,如果該ObservableSource返回onComplete/onError,那麼不會觸發重訂閱;如果發送onNext,那麼會觸發重訂閱。

實現retryWhen的關鍵在於如何定義它的Function參數:
• Function的輸入是一個Observable,輸出是一個泛型ObservableSource。如果我們接收Observable發送的消息,那麼就可以得到上游發送的錯誤類型,並根據該類型進行響應的處理。
• 如果輸出的Observable發送了onComplete或者onError則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。也就是說,它 僅僅是作爲一個是否要觸發重訂閱的通知,onNext發送的是什麼數據並不重要。
• 對於每一次訂閱的數據流 Function 函數只會回調一次,並且是在onError(Throwable throwable)的時候觸發,它不會收到任何的onNext事件。
• 在Function函數中,必須對輸入的 Observable進行處理,這裏我們使用的是flatMap操作符接收上游的數據。

4.3 retryWhen 和 repeatWhen 對比

retryWhen和repeatWhen最大的不同就是:retryWhen是收到onError後觸發是否要重訂閱的詢問,而repeatWhen是通過onComplete觸發。

5. combineLatest 實現的輸入表單驗證

在下面這個示例中,包含了兩個輸入框,分別對應用戶名和密碼,它們的長度要求分別爲2~8和4~16,如果兩者都正確,那麼登錄按鈕的文案變爲“登錄”,否則顯示“用戶名或密碼無效”。

  mEtName.addTextChangedListener(new EditTextMonitor(mNameSubject));
        mEtPassword.addTextChangedListener(new EditTextMonitor(mPasswordSubject));
        Observable<Boolean> observable = Observable.combineLatest(mNameSubject, 
        mPasswordSubject, new BiFunction<String, String, Boolean>() {

            @Override
            public Boolean apply(String name, String password) throws Exception {
                int nameLen = name.length();
                int passwordLen = password.length();
                return nameLen >= 2 && nameLen <= 8 && passwordLen >= 4 
                && passwordLen <= 16;
            }

        });
        DisposableObserver<Boolean> disposable = new DisposableObserver<Boolean>() {

            @Override
            public void onNext(Boolean value) {
                mBtLogin.setText(value ? "登錄" : "用戶名或密碼無效");
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }

        };
        observable.subscribe(disposable);

private class EditTextMonitor implements TextWatcher {

        private PublishSubject<String> mPublishSubject;

        EditTextMonitor(PublishSubject<String> publishSubject) {
            mPublishSubject = publishSubject;
        }

        @Override
        public void afterTextChanged(Editable s) {
            mPublishSubject.onNext(s.toString());
        }
    }

我們首先創建了兩個PublishSubject,分別用於用戶名和密碼的訂閱,然後通過combineLatest對這兩個PublishSubject進行組合。這樣,當任意一個PublishSubject發送事件之後,就會回調combineLatest最後一個函數的apply方法,該方法會取到每個被觀察的PublishSubject最後一次發射的數據,我們通過該數據進行驗證。

zip和combineLatest接收的參數格式相同, zip和combineLatest的區別在於:
• zip是在其中一個Observable發射數據項後,組合所有Observable最早一個未被組合的數據項,也就是說,組合後的Observable發射的第n個數據項,必然是每個源由Observable各自發射的第n個數據項構成的。
• combineLatest則是在其中一個Observable發射數據項後,組合所有Observable所發射的最後一個數據項(前提是所有的Observable都至少發射過一個數據項)。

6.使用 publish + merge 優化先加載緩存,再讀取網絡數據的請求過程

爲了讓大家對這一過程有更深刻的理解,我們介紹”先加載緩存,再請求網絡”這種模型的四種實現方式,其中第四種實現可以達到上面我們所說的效果,而前面的三種實現雖然也能夠實現相同的需求,並且可以正常工作,但是在某些特殊情況下,會出現意想不到的情況:
• 使用concat實現
• 使用concatEager實現
• 使用merge實現
• 使用publish實現

6.1 準備工作

我們需要準備兩個Observable,分別表示 緩存數據源 和 網絡數據源,在其中填入相應的緩存數據和網絡數據,爲了之後演示一些特殊的情況,我們可以在創建它的時候指定它執行的時間:

//模擬緩存數據源。
    private Observable<List<NewsResultEntity>> getCacheArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "開始加載緩存數據");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("緩存");
                        entity.setDesc("序號=" + i);
                        results.add(entity);
                    }
                    observableEmitter.onNext(results);
                    observableEmitter.onComplete();
                    Log.d(TAG, "結束加載緩存數據");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        });
    }
    //模擬網絡數據源。
    private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "開始加載網絡數據");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("網絡");
                        entity.setDesc("序號=" + i);
                        results.add(entity);
                    }
                    observableEmitter.onNext(results);
                    observableEmitter.onComplete();
                    Log.d(TAG, "結束加載網絡數據");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        });
    }

6.2 使用 concat 實現

concat是很多文章都推薦使用的方式,因爲它不會有任何問題,實現代碼如下(注意不發complete事件,第二個不會收到事件):

private void refreshArticleUseContact() {
        Observable<List<NewsResultEntity>> contactObservable = Observable.concat(
                getCacheArticle(500).subscribeOn(Schedulers.io()), 
                getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        DisposableObserver<List<NewsResultEntity>> disposableObserver = 
        getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

從控制檯的輸出可以看到,整個過程是先取讀取緩存,等緩存的數據讀取完畢之後,纔開始請求網絡,因此整個過程的耗時爲兩個階段的相加,即2500ms。
這裏寫圖片描述
那麼,concat操作符的缺點是什麼呢?很明顯,我們白白浪費了前面讀取緩存的這段時間,能不能同時發起讀取緩存和網絡的請求,而不是等到讀取緩存完畢之後,纔去請求網絡呢?

6.3 使用 concatEager 實現

private void refreshArticleUseConcatEager() {
        List<Observable<List<NewsResultEntity>>> observables = new ArrayList<>();
        observables.add(getCacheArticle(500).subscribeOn(Schedulers.io()));
        observables.add(getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        Observable<List<NewsResultEntity>> contactObservable = 
        Observable.concatEager(observables);
        DisposableObserver<List<NewsResultEntity>> disposableObserver = 
        getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

它和concat最大的不同就是多個Observable可以同時開始發射數據,如果後一個Observable發射完成後,前一個Observable還沒有發射完數據,那麼它會將後一個Observable的數據先緩存起來,等到前一個Observable發射完畢後,纔將緩存的數據發射出去。

那麼這種實現方式的缺點是什麼呢?就是在某些異常情況下,如果讀取緩存的時間要大於網絡請求的時間,那麼就會導致出現“網絡請求的結果”等待“讀取緩存”這一過程完成後才能傳遞給下游,白白浪費了一段時間。

6.4 使用 merge 實現

private void refreshArticleUseMerge() {
        Observable<List<NewsResultEntity>> contactObservable 
        = Observable.merge(getCacheArticle(500).subscribeOn(Schedulers.io()), 
        getNetworkArticle(2000).subscribeOn(Schedulers.io()));
        DisposableObserver<List<NewsResultEntity>> disposableObserver
         = getArticleObserver();
        contactObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

它和concatEager一樣,會讓多個Observable同時開始發射數據,但是它不需要Observable之間的互相等待,而是直接發送給下游。所以可能網絡數據加載完成後,讀取的緩存數據會將網絡數據覆蓋。

6.5 使用 publish 實現

private void refreshArticleUsePublish() {
        Observable<List<NewsResultEntity>> publishObservable 
= getNetworkArticle(2000)
.subscribeOn(Schedulers.io())
.publish(new Function<Observable<List<NewsResultEntity>>, 
ObservableSource<List<NewsResultEntity>>>() {

            @Override
            public ObservableSource<List<NewsResultEntity>> 
            apply(Observable<List<NewsResultEntity>> network) throws Exception {
                return Observable
                .merge(network,getCacheArticle(500)
                .subscribeOn(Schedulers.io())
                .takeUntil(network));
            }

        });
        DisposableObserver<List<NewsResultEntity>>
         disposableObserver = getArticleObserver();
        publishObservable.observeOn(AndroidSchedulers.mainThread())
        .subscribe(disposableObserver);
    }

這裏面一共涉及到了三個操作符,publish、merge和takeUnti,我們先來看一下它能否解決我們之前三種方式的缺陷:
• 讀取緩存的時間爲500ms,請求網絡的時間爲2000ms

這裏寫圖片描述

• 讀取緩存的時間爲2000ms,請求網絡的時間爲500ms
這裏寫圖片描述

如果網絡請求先返回時發生了錯誤(例如沒有網絡等)導致發送了onError事件,從而使得緩存的Observable也無法發送事件,最後界面顯示空白。
針對這個問題,我們需要對網絡的Observable進行優化,讓其不將onError事件傳遞給下游。其中一種解決方式是通過使用onErrorResume操作符,它可以接收一個Func函數,其形參爲網絡發送的錯誤,而在上游發生錯誤時會回調該函數。我們可以根據錯誤的類型來返回一個新的Observable,讓訂閱者鏡像到這個新的Observable,並且忽略onError事件,從而避免onError事件導致整個訂閱關係的結束。
這裏爲了避免訂閱者在鏡像到新的Observable時會收到額外的時間,我們返回一個Observable.never(),它表示一個永遠不發送事件的上游。

private Observable<List<NewsResultEntity>> getNetworkArticle(final long simulateTime) {
        return Observable.create(new ObservableOnSubscribe<List<NewsResultEntity>>() {
            @Override
            public void subscribe(
                ObservableEmitter<List<NewsResultEntity>> observableEmitter) throws Exception {
                try {
                    Log.d(TAG, "開始加載網絡數據");
                    Thread.sleep(simulateTime);
                    List<NewsResultEntity> results = new ArrayList<>();
                    for (int i = 0; i < 10; i++) {
                        NewsResultEntity entity = new NewsResultEntity();
                        entity.setType("網絡");
                        entity.setDesc("序號=" + i);
                        results.add(entity);
                    }
                    //a.正常情況。
                    //observableEmitter.onNext(results);
                    //observableEmitter.onComplete();
                    //b.發生異常。
                    observableEmitter.onError(new Throwable("netWork Error"));
                    Log.d(TAG, "結束加載網絡數據");
                } catch (InterruptedException e) {
                    if (!observableEmitter.isDisposed()) {
                        observableEmitter.onError(e);
                    }
                }
            }
        }).onErrorResumeNext(new Function<Throwable, 
            ObservableSource<? extends List<NewsResultEntity>>>() {

            @Override
            public ObservableSource<? extends List<NewsResultEntity>> 
                        apply(Throwable throwable) throws Exception {
                Log.d(TAG, "網絡請求發生錯誤throwable=" + throwable);
                return Observable.never();
            }
        });
    } 

6.5.1 takeUntil

我們給sourceObservable通過takeUntil傳入了另一個otherObservable,它表示sourceObservable在otherObservable發射數據之後,就不允許再發射數據了,這就剛好滿足了我們前面說的“只要網絡源發送了數據,那麼緩存源就不應再發射數據”。
之後,我們再用前面介紹過的merge操作符,讓兩個緩存源和網絡源同時開始工作,去取數據。

6.5.2 publish

但是上面有一點缺陷,就是調用merge和takeUntil會發生兩次訂閱,這時候就需要使用publish操作符,它接收一個Function函數,該函數返回一個Observable,該Observable是對原Observable,也就是上面網絡源的Observable轉換之後的結果,該Observable可以被takeUntil和merge操作符所共享,從而實現只訂閱一次的效果。

7. 使用 timer/interval/delay 實現任務調度

• timer:創建型操作符,用於延時執行任務。
• interval:創建型操作符,用於週期執行任務。
• delay:輔助型操作,用於延時傳遞數據。

//延遲 1s 後執行一個任務,然後結束
Observable.timer(1000, TimeUnit.MILLISECONDS)
        .subscribeOn(Schedulers.io())
        .subscribe(disposableObserver);

每隔1s執行一次任務,第一次任務執行前有1s的間隔,執行無限次。這是因爲,使用interval操作符時,默認第一次個任務需要延時和指定間隔相同的時間。

 Observable.interval(1000, TimeUnit.MILLISECONDS)
             .subscribeOn(Schedulers.io())
             .subscribe(disposableObserver);

如果希望立即執行第一次任務,那麼可以給它提供額外的參數,指定第一次任務的延時:

 Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
         .subscribeOn(Schedulers.io())
         .subscribe(disposableObserver);

每隔 1s 執行一次任務,立即執行第一次任務,只執行五次:

  Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
      .take(5)
      .subscribe(disposableObserver);

delay
當它接受一個時間段時,每當原始的Observable發射了一個數據項時,它就啓動一個定時器,等待指定的時間後再將這個數據發射出去,因此表現爲發射的數據項進行了平移,但是它只會平移onNext/onComplete,對於onError,它會立即發射出去,並且丟棄之前等待發射的onNext事件。

因爲delay不是創建型操作符,所以我們可以用來延遲上游發射過來的數據,下面,讓我們實現這個效果:先執行一個任務,等待 1s,再執行另一個任務,然後結束。代碼如下:

 //先執行一個任務,等待 1s,再執行另一個任務,然後結束
    private void startTimeDemo5() {
        Log.d(TAG, "startTimeDemo5");
        DisposableObserver<Long> disposableObserver = getTimeDemoObserver();
        Observable.just(0L).doOnNext(new Consumer<Long>() {

            @Override
            public void accept(Long aLong) throws Exception {
                Log.d(TAG, "執行第一個任務");
            }

        }).delay(1000, TimeUnit.MILLISECONDS).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

執行效果爲:
這裏寫圖片描述

8.屏幕旋轉導致 Activity 重建時恢復任務

如果我們在AndroidManifest.xml中聲明Activity時,沒有對android:configChanges進行特殊的聲明,那麼在屏幕旋轉時,會導致Activity的重建,幾個關鍵聲明週期的調用情況如下所示:

這裏寫圖片描述
旋轉屏幕前的Activity中的變量都會被銷燬,但是有時候我們某些任務的執行不和Activity的生命週期綁定,這時候我們就可以利用Fragment提供的setRetainInstance方法, 如果給Fragment設置了該標誌位,那麼在屏幕旋轉之後,雖然它依附的Activity被銷燬了,但是該Fragment的實例會被保留,並且在Activity的銷燬過程中,只會調用該Fragment的onDetach方法,而不會調用onDestroy方法。
而在Activity重建時,會調用該Fragment實例的onAttach、onActivityCreated方法,但不會調用onCreate方法。

首先,我們聲明一個接口,使得Activity可以監聽到Fragment中後臺任務的工作進度:

public interface IHolder {
    public void onWorkerPrepared(ConnectableObservable<Long> workerFlow);
}

下面,我們來實現WorkerFragment,我們在onCreate中創建了數據源,它每隔1s向下遊發送數據,在onResume中,通過前面定義的接口向Activity傳遞一個ConnectableObservable用於監聽。這裏最關鍵的是需要調用我們前面說到的setRetainInstance方法,最後別忘了,在onDetach中將mHolder置爲空,否則它就會持有需要被重建的Activity示例,從而導致內存泄漏。

public class WorkerFragment extends Fragment {

    public static final String TAG = WorkerFragment.class.getName();

    private ConnectableObservable<String> mWorker;
    private Disposable mDisposable;
    private IHolder mHolder;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof IHolder) {
            mHolder = (IHolder) context;
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        if (mWorker != null) {
            return;
        }
        Bundle bundle = getArguments();
        final String taskName = (bundle != null ? bundle.getString("task_name") : null);
        mWorker = Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> 
                                            observableEmitter) throws Exception {

                for (int i = 0; i < 10; i++) {
                    String message = "任務名稱=" + taskName + ", 任務進度=" + i * 10 + "%";
                    try {
                        Log.d(TAG, message);
                        Thread.sleep(1000);
                        //如果已經拋棄,那麼不再繼續任務。
                        if (observableEmitter.isDisposed()) {
                            break;
                        }
                    } catch (InterruptedException error) {
                        if (!observableEmitter.isDisposed()) {
                            observableEmitter.onError(error);
                        }
                    }
                    observableEmitter.onNext(message);
                }
                observableEmitter.onComplete();
            }

        }).subscribeOn(Schedulers.io())

        .publish();
        mDisposable = mWorker.connect();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mHolder != null) {
            mHolder.onWorkerPrepared(mWorker);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mDisposable.dispose();
        Log.d(TAG, "onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mHolder = null;
    }
}

最後來看Activity,當點擊“開始工作任務”後,我們嘗試添加WorkerFragment,這時候就會走到WorkerFragment的onCreate方法中啓動任務,之後當WorkerFragment走到onResume方法後,就調用onWorkerPrepared,讓Activity進行訂閱,Activity就可以收到當前任務進度的通知,來更新UI。而在任務執行完畢之後,我們就可以將該Fragment移除了。

public class RotationPersistActivity extends AppCompatActivity implements IHolder {

    private static final String TAG = RotationPersistActivity.class.getName();

    private Button mBtWorker;
    private TextView mTvResult;
    private CompositeDisposable mCompositeDisposable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_rotation_persist);
        mBtWorker = (Button) findViewById(R.id.bt_start_worker);
        mBtWorker.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                startWorker();
            }

        });
        mTvResult = (TextView) findViewById(R.id.tv_worker_result);
        mCompositeDisposable = new CompositeDisposable();
    }

    @Override
    public void onWorkerPrepared(Observable<String> worker) {
        DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {

            @Override
            public void onNext(String message) {
                mTvResult.setText(message);
            }

            @Override
            public void onError(Throwable throwable) {
                onWorkerFinished();
                mTvResult.setText("任務錯誤");
            }

            @Override
            public void onComplete() {
                onWorkerFinished();
                mTvResult.setText("任務完成");
            }

        };
        worker.observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    private void startWorker() {
        WorkerFragment worker = getWorkerFragment();
        if (worker == null) {
            addWorkerFragment();
        } else {
            Log.d(TAG, "WorkerFragment has attach");
        }
    }

    private void onWorkerFinished() {
        Log.d(TAG, "onWorkerFinished");
        removeWorkerFragment();
    }

    private void addWorkerFragment() {
        WorkerFragment workerFragment = new WorkerFragment();
        Bundle bundle = new Bundle();
        bundle.putString("task_name", "學習RxJava2");
        workerFragment.setArguments(bundle);
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.add(workerFragment, WorkerFragment.TAG);
        transaction.commit();
    }

    private void removeWorkerFragment() {
        WorkerFragment workerFragment = getWorkerFragment();
        if (workerFragment != null) {
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            transaction.remove(workerFragment);
            transaction.commit();
        }
    }

    private WorkerFragment getWorkerFragment() {
        FragmentManager manager = getSupportFragmentManager();
        return (WorkerFragment) manager.findFragmentByTag(WorkerFragment.TAG);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
        mCompositeDisposable.clear();
    }
}

而在我們的應用場景中,由於WorkerFragment是在後臺執行任務:
• 從Activity的角度來看:每次Activity重建時,在Activity中都需要用一個新的Observer實例去訂閱WorkerFragment中的數據源,因此我們只能選擇通過Hot Observable,而不是Cold Observable來實現WorkerFragment中的數據源,否則每次都會重新執行一遍數據流的代碼,而不是繼續接收它發送的事件。
• 從WorkerFragment的角度來看,它只是一個任務的執行者,不管有沒有人在監聽它的進度,它都應該執行任務。
通過Observable.create方法創建的是一個Cold Observable,該Cold Observable每隔1s發送一個事件。我們調用publish方法來將它轉換爲Hot Observable,之後再調用該Hot Observable的connect方法讓其對源Cold Observable進行訂閱,這樣源Cold Observable就可以開始執行任務了。並且,通過connect方法返回的Disposable對象,我們就可以管理轉換後的Hot Observable和源Cold Observable之間的訂閱關係。
Disposable的用途在於:在某些時候,我們希望能夠停止源Observable任務的執行,例如當該WorkerFragment真正被銷燬時,也就是執行了它的onDestroy方法,那麼我們就可以通過上面的Disposable取消Hot Observable對源Cold Observable的訂閱,而在Cold Observable的循環中,我們判斷如果下游(也就是Hot Observable)取消了訂閱,那麼就不再執行任務

在任務執行之後 removeFragment
讓WorkerFragment能正常進行下一次任務的執行,我們需要在發生錯誤或者任務完成的時候,通過remove的方式銷燬WorkerFragment。

9 檢測網絡狀態並自動重試請求

  • 在應用啓動時,我們會啓動定位模塊,該定位模塊在後臺每隔一段時間發起一次定位請求,拿到定位的結果後,我們通過該城市向服務器發起請求,以獲取對應城市的天氣信息進行展示。
  • 但是在拿到城市之後向服務器請求天氣的過程中有可能是處於沒有網絡的狀態,導致無法獲取城市的天氣信息並刷新界面,因此,我們需要檢測網絡的狀態,在網絡重連的時候,讀取上一次緩存的城市,向服務器發起請求以獲取城市對應天氣信息。

    這裏寫圖片描述

9.1 定位模塊

我們通過一個後臺線程來模擬定位的過程,它每隔一段時間獲取一次定位的結果,並將該結果通過mCityPublish發送數據給它的訂閱者。

 //用於發佈定位到的城市結果。
    private PublishSubject<Long> mCityPublish;

    //模擬定位模塊的回調。
    private void startUpdateLocation() {
        mLocationThread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        for (long cityId : CITY_ARRAY) {
                            if (isInterrupted()) {
                                break;
                            }
                            Log.d(TAG, "重新定位");
                            Thread.sleep(5000);
                            Log.d(TAG, "定位到城市信息=" + cityId);
                            mCityPublish.onNext(cityId);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        mLocationThread.start();
    }

在mCityPublish發送消息到訂閱者收到消息之間,我們還需要做一些特殊的處理:

 private Observable<Long> getCityPublish() {
        return mCityPublish.distinctUntilChanged()
                .doOnNext(new Consumer<Long>() {

                    @Override
                    public void accept(Long aLong) throws Exception {
                        saveCacheCity(aLong);
                    }

                });
    }

這裏我們做了兩步處理:
• 使用distinctUntilChanged對定位結果進行過濾,如果此次定位的結果和上次定位的結果相同,那麼不通知訂閱者。
• 使用doOnNext,在返回結果給訂閱者之前,先把最新一次的定位結果存儲起來,用於在之後網絡重連之後進行請求。

9.2 網絡狀態模塊

與定位模塊類似,我們也需要一個mNetStatusPublish,其類型爲PublishSubject,它在網絡狀態發生變化時通知訂閱者。這裏需要註冊一個廣播,在收到廣播之後,我們通過mNetStatusPublish通知訂閱者,代碼如下:

  private void registerBroadcast() {
        mReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                if (mNetStatusPublish != null) {
                    mNetStatusPublish.onNext(isNetworkConnected());
                }
            }

        };
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        registerReceiver(mReceiver, filter);
    }

在收到網絡狀態變化的消息之後:

  private Observable<Long> getNetStatusPublish() {
        return mNetStatusPublish.filter(new Predicate<Boolean>() {

            @Override
            public boolean test(Boolean aBoolean) throws Exception {
                return aBoolean && getCacheCity() > 0;
            }

        }).map(new Function<Boolean, Long>() {

            @Override
            public Long apply(Boolean aBoolean) throws Exception {
                return getCacheCity();
            }

        }).subscribeOn(Schedulers.io());
    }

這裏我們做了兩步處理:
• 使用filter對消息進行過濾,只有在 聯網情況並且之前已經定位到了城市 之後才通知訂閱者,filter的原理圖如下所示,該操作符用於過濾掉一些不需要的數據:

• 使用map,讀取當前緩存的城市名,返回給訂閱者,map的原理圖如下所示,該操作符可以用於執行變換操作。

9.3 網絡請求模塊

在9.1和9.2中,我們分別用getCityPublish()和getNetStatusPublish()來獲取被訂閱者,它們分別對應於定位模塊和網絡狀態模塊發生變化時所發送的城市數據,下面來看我們通過城市數據獲取城市天氣信息的代碼:

 private void startUpdateWeather() {
        Observable.merge(getCityPublish(), getNetStatusPublish())
        .flatMap(new Function<Long, ObservableSource<WeatherEntity>>() {

            @Override
            public ObservableSource<WeatherEntity> 
                      apply(Long aLong) throws Exception {
                        Log.d(TAG, "嘗試請求天氣信息=" + aLong);
                        return getWeather(aLong).subscribeOn(Schedulers.io());
            }

        })
        .retryWhen(new Function<Observable<Throwable>, 
                                    ObservableSource<?>>() {

            @Override
            public ObservableSource<?> apply(Observable<Throwable> 
                    throwableObservable) throws Exception {
                return throwableObservable
                .flatMap(new Function<Throwable, ObservableSource<?>>() {

                    @Override
                    public ObservableSource<?> apply(Throwable throwable) 
                            throws Exception {
                        Log.d(TAG, "請求天氣信息過程中發生錯誤,進行重訂閱");
                        return Observable.just(0);
                    }

                });
            }

        }).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherEntity>() {

            @Override
            public void onSubscribe(Disposable disposable) {
                mCompositeDisposable.add(disposable);
            }

            @Override
            public void onNext(WeatherEntity weatherEntity) {
                WeatherEntity.WeatherInfo info = weatherEntity.getWeatherinfo();
                if (info != null) {
                    Log.d(TAG, "嘗試請求天氣信息成功");
                    StringBuilder builder = new StringBuilder();
                    builder.append("城市名:")....;
                    mTvNetworkResult.setText(builder.toString());
                }
            }

            @Override
            public void onError(Throwable throwable) {
                Log.d(TAG, "嘗試請求天氣信息失敗");
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "嘗試請求天氣信息結束");
            }
        });
    }

    private Observable<WeatherEntity> getWeather(long cityId) {
        WeatherApi api = new Retrofit.Builder()
                .baseUrl("http://www.weather.com.cn/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build().create(WeatherApi.class);
        return api.getWeather(cityId);
    }

這裏我們做了以下幾個操作:
• 使用merge合併兩個數據源。
• 使用retryWhen進行重訂閱,因爲在獲取到城市,之後轉換成城市天氣信息的時候有可能發生錯誤,如果發生了錯誤,那麼整個調用鏈就結束了,需要重新訂閱。
這裏寫圖片描述

10. 實戰講解 publish & replay & share & refCount & autoConnect

今天,我們來整理以下幾個大家容易弄混的概念:
• 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。

10.1 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沒有發送任何數據。
• 第二步: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 會重新開始發射數據給該訂閱者。

10.2由 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發送的數據轉交給它的訂閱者。*

10.3由 ConnectableObservable 轉換成 Observable

由ConnectableObservable轉換成Observable有兩種方法,我們分爲兩節介紹下當訂閱到轉換後的Observable時的現象:
• .refCount()
• .autoConnect(int N)

10.3.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是等價的。

10.3.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停止發送數據。

10.4 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的replay(3)方法創建ConnectableObservable,可以看到,此時雖然ConnectableObservable沒有任何訂閱者,但是Cold Observable也已經開始發送數據。
• 第二步:Observer1訂閱到ConnectableObservable,此時它會先收到之前發射的3個數據,之後收到訂閱之後Cold Observable發射的數據。

11.錯誤發生時不自動停止訂閱關係

在RxJava中,如果發生了錯誤,那麼 訂閱者會自動停止對上游的訂閱關係 ,我們將導致訂閱取消的錯誤分爲兩種:
• 上游:上游發生錯誤,併發送onError事件給訂閱者。
• 下游:訂閱者在onNext中處理時發生了異常。
在RxJava的設計中,如果發生了錯誤,那麼訂閱關係就取消了。但是在某些時候,我們希望在錯誤發生的時候不要取消訂閱,因爲這樣訂閱者只有重新通過subscribe方法才能收到消息,類似的場景如監測數據源變化、RxBus的實現等。
我們先用兩個簡單的例子來演示一下上面提到的兩種情況:

11.1 上游發生錯誤

在上游發生錯誤的時候,一般通過重訂閱的方式來解決。我們可以根據錯誤的類型判斷是否需要重訂閱,重訂閱的時候使用retryWhen操作符:

 private void upErrorIgnore() {
        mPublishSubject.map(new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) throws Exception {
                if (integer == 4) {
                    throw new RuntimeException("retry");
                } else if (integer == 8) {
                    throw new RuntimeException("don't retry");
                }
                return integer;
            }
        }).observeOn(AndroidSchedulers.mainThread())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(Observable<Throwable> throwableObservable) 
                                                                throws Exception {
                //第一步,通過flatMap對錯誤進行響應。
                return throwableObservable
                        .flatMap(new Function<Throwable, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Throwable throwable) throws Exception {
                        //第二步:根據錯誤的類型判斷是否需要重訂閱。
                        return "retry".equals(throwable.getMessage())
                                                 ? Observable.just(0) : Observable.empty();
                    }
                });
            }
        }).subscribe(getNormalObserver());
    }

在第四次/第八次點擊的是否,我們分別在上游拋出一個異常,這樣就會觸發retryWhen的回調,在其中我們分爲註釋中的兩部分進行處理,第四次的時候發起重訂閱,而第八次則不發起,因此,第九個事件訂閱者就收不到了,控制檯的輸出爲:
這裏寫圖片描述

11.2 訂閱者發生錯誤

但是retryWhen只能處理上游發生錯誤的情況,對於上面說的第二種情況並不能處理,因此假如是上面介紹的第二種情況:訂閱者在onNext處理中發生錯誤的情況,仍然會解除訂閱關係,這是因爲在LambdaObserver的源碼中,如果在onNext中發生了異常,那麼首先會調用onError方法,而onError中會執行取消訂閱的操作。
這裏寫圖片描述
解決辦法就是,把 LambdaObserver 代碼拷貝出來,註釋掉那句,然後繼承於它去實現 Observer。
這裏寫圖片描述
從控制檯可以看出,並沒有解除訂閱關係,在發生錯誤之後,仍然可以繼續收到數據。
這裏寫圖片描述

12. 刷新過期 token 並重新發起請求

• 在重試之前,需要先去刷新一次token,而不是單純地等待一段時間再重試。
• 如果有多個請求都出現了因token失效而需要重新刷新token的情況,那麼需要判斷當前是否有另一個請求正在刷新token,如果有,那麼就不要發起刷新token的請求,而是等待刷新token的請求返回後,直接進行重試。
直接上代碼了:

12.1 Token 存儲模塊

一個Store 類,代碼簡單,省略邏輯

12.2 依賴於 token 的接口

這裏,我們用一個簡單的getUserObservable來模擬依賴於token的接口,token存儲的是獲取的時間,爲了演示方便,我們設置如果距離上次獲取的時間大於2s,那麼就認爲過期,並拋出token失效的錯誤,否則調用onNext方法返回接口給下游。

 private Observable<String> getUserObservable (final int index, final String token) {
        return Observable.create(new ObservableOnSubscribe<String>() {

            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                Log.d(TAG, index + "使用token=" + token + "發起請求");
                //模擬根據Token去請求信息的過程。
                if (!TextUtils.isEmpty(token) 
                                            && System.currentTimeMillis() - Long.valueOf(token) < 2000) {
                    e.onNext(index + ":" + token + "的用戶信息");
                } else {
                    e.onError(new Throwable(ERROR_TOKEN));
                }
            }
        });
    }

12.3 完整的請求過程

下面,我們來看一下整個完整的請求過程:

  private void startRequest(final int index) {
        Observable<String> observable = Observable.defer(
        new Callable<ObservableSource<String>>() {
            @Override
            public ObservableSource<String> call() throws Exception {
                String cacheToken = TokenLoader.getInstance().getCacheToken();
                Log.d(TAG, index + "獲取到緩存Token=" + cacheToken);
                return Observable.just(cacheToken);
            }
        }).flatMap(new Function<String, ObservableSource<String>>() {
            @Override
            public ObservableSource<String> apply(String token) throws Exception {
                return getUserObservable(index, token);
            }
        }).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

            private int mRetryCount = 0;

            @Override
            public ObservableSource<?> apply(Observable<Throwable> throwableObservable) 
                                                                                                        throws Exception {
                return throwableObservable.flatMap(
                new Function<Throwable, ObservableSource<?>>() {

                    @Override
                    public ObservableSource<?> apply(Throwable throwable) throws Exception {
                        Log.d(TAG, index + ":" + "發生錯誤=" + throwable +
                                 ",重試次數=" + mRetryCount);
                        if (mRetryCount > 0) {
                            return Observable.error(new Throwable(ERROR_RETRY));
                        } else if (ERROR_TOKEN.equals(throwable.getMessage())) {
                            mRetryCount++;
                            return TokenLoader.getInstance().getNetTokenLocked();
                        } else {
                            return Observable.error(throwable);
                        }
                    }
                });
            }
        });
        DisposableObserver<String> observer = new DisposableObserver<String>() {

            @Override
            public void onNext(String value) {
                Log.d(TAG, index + ":" + "收到信息=" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, index + ":" + "onError=" + e);
            }

            @Override
            public void onComplete() {
                Log.d(TAG, index + ":" + "onComplete");
            }
        };
        observable.subscribeOn(Schedulers.io())
        .subscribe(observer);
    }

爲了方便大家閱讀,我把所有的邏輯都寫在了一整個調用鏈裏,整個調用鏈分爲四個部分:
• defer:讀取緩存中的token信息,這裏調用了TokenLoader中讀取緩存的接口,而這裏使用defer操作符,是爲了在重訂閱時,重新創建一個新的Observable,以讀取最新的緩存token信息,其原理圖如下:
• flatMap:通過token信息,請求必要的接口。
• retryWhen:使用重訂閱的方式來處理token失效時的邏輯,這裏分爲三種情況:重試次數到達,那麼放棄重訂閱,直接返回錯誤;請求token接口,根據token請求的結果決定是否重訂閱;其它情況直接放棄重訂閱。
• subscribe:返回接口數據。

最後

關鍵點在於TokenLoader的實現邏輯,代碼如下:

public class TokenLoader {

    private static final String TAG = TokenLoader.class.getSimpleName();

    private AtomicBoolean mRefreshing = new AtomicBoolean(false);
    private PublishSubject<String> mPublishSubject;
    private Observable<String> mTokenObservable;

    private TokenLoader() {
        mPublishSubject = PublishSubject.create();
        mTokenObservable = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                Thread.sleep(1000);
                Log.d(TAG, "發送Token");
                e.onNext(String.valueOf(System.currentTimeMillis()));
            }
        }).doOnNext(new Consumer<String>() {
            @Override
            public void accept(String token) throws Exception {
                Log.d(TAG, "存儲Token=" + token);
                Store.getInstance().setToken(token);
                mRefreshing.set(false);
            }
        }).doOnError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                mRefreshing.set(false);
            }
        }).subscribeOn(Schedulers.io());
    }

    public static TokenLoader getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final TokenLoader INSTANCE = new TokenLoader();
    }

    public String getCacheToken() {
        return Store.getInstance().getToken();
    }

    public Observable<String> getNetTokenLocked() {
        if (mRefreshing.compareAndSet(false, true)) {
            Log.d(TAG, "沒有請求,發起一次新的Token請求");
            startTokenRequest();
        } else {
            Log.d(TAG, "已經有請求,直接返回等待");
        }
        return mPublishSubject;
    }

    private void startTokenRequest() {
        mTokenObservable.subscribe(mPublishSubject);
    }

}

在retryWhen中,我們調用了getNetTokenLocked來獲得一個PublishSubject,爲了實現前面說到的下面這個邏輯:

這裏寫圖片描述
我們使用了一個AtomicBoolean來標記是否有刷新Token的請求正在執行,如果有,那麼直接返回一個PublishSubject,否則就先發起一次刷新token的請求,並將PublishSubject作爲該請求的訂閱者。
這裏用到了PublishSubject的特性,它既是作爲Token請求的訂閱者,同時又作爲retryWhen函數所返回Observable的發送方,因爲retryWhen返回的Observable所發送的值就決定了是否需要重訂閱:
• 如果Token請求返回正確,那麼就會發送onNext事件,觸發重訂閱操作,使得我們可以再次觸發一次重試操作。
• 如果Token請求返回錯誤,那麼就會放棄重訂閱,使得整個請求的調用鏈結束。
而AtomicBoolean保證了多線程的情況下,只能有一個刷新Token的請求,在這個階段內不會觸發重複的刷新token請求,僅僅是作爲觀察者而已,並且可以在刷新token的請求回來之後立刻進行重訂閱的操作。在doOnNext/doOnError中,我們將正在刷新的標誌位恢復,同時緩存最新的token。
爲了模擬上面提到的多線程請求刷新token的情況,我們在發起一個請求500ms之後,立刻發起另一個請求,當第二個請求決定是否要重訂閱時,第一個請求正在進行刷新token的操作。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_token);
        mBtnRequest = (Button) findViewById(R.id.bt_request);
        mBtnRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startRequest(0);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                startRequest(1);
            }
        });
    }

控制檯的輸出如下,可以看到在第二個請求決定是否要重訂閱時,它判斷到已經有請求,因此只是等待而已。而在第一個請求導致的token刷新回調之後,兩個請求都進行了重試,併成功地請求到了接口信息。
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章