RxJava2 實戰知識梳理(5) - 簡單及進階的輪詢操作

RxJava2 實戰系列文章

RxJava2 實戰知識梳理(1) - 后臺執行耗時操作,實時通知 UI 更新
RxJava2 實戰知識梳理(2) - 計算一段時間內數據的平均值
RxJava2 實戰知識梳理(3) - 優化搜索聯想功能
RxJava2 實戰知識梳理(4) - 結合 Retrofit 請求新聞資訊
RxJava2 實戰知識梳理(5) - 簡單及進階的輪詢操作
RxJava2 實戰知識梳理(6) - 基于錯誤類型的重試請求
RxJava2 實戰知識梳理(7) - 基于 combineLatest 實現的輸入表單驗證
RxJava2 實戰知識梳理(8) - 使用 publish + merge 優化先加載緩存,再讀取網絡數據的請求過程
RxJava2 實戰知識梳理(9) - 使用 timer/interval/delay 實現任務調度
RxJava2 實戰知識梳理(10) - 屏幕旋轉導致 Activity 重建時恢復任務
RxJava2 實戰知識梳理(11) - 檢測網絡狀態并自動重試請求
RxJava2 實戰知識梳理(12) - 實戰講解 publish & replay & share & refCount & autoConnect
RxJava2 實戰知識梳理(13) - 如何使得錯誤發生時不自動停止訂閱關系
RxJava2 實戰知識梳理(14) - 在 token 過期時,刷新過期 token 并重新發起請求
RxJava2 實戰知識梳理(15) - 實現一個簡單的 MVP + RxJava + Retrofit 應用


一、示例

1.1 應用場景

今天,我們介紹一種新的場景,輪詢操作。也就是說,我們會嘗試間隔一段時間就向服務器發起一次請求,在使用RxJava之前,該需求的實現一般有兩種方式:

  • 通過Handler發送延時消息,在handleMessage中請求服務器之后,再次發送一個延時消息,直到達到循環次數為止。
  • 使用Java提供的定時器Timer

我們嘗試使用RxJava2提供的操作符來實現這一需求,這里演示兩種方式的輪詢,并將單次訪問的次數限制在5次:

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

2.2 示例

public class PollingActivity extends AppCompatActivity {

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

    private TextView mTvSimple;
    private TextView mTvAdvance;
    private CompositeDisposable mCompositeDisposable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_polling);
        mTvSimple = (TextView) findViewById(R.id.tv_simple);
        mTvSimple.setOnClickListener(new View.OnClickListener() {

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

        });
        mTvAdvance = (TextView) findViewById(R.id.tv_advance);
        mTvAdvance.setOnClickListener(new View.OnClickListener() {

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

        });
        mCompositeDisposable = new CompositeDisposable();
    }

    private void startSimplePolling() {
        Log.d(TAG, "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 {
                doWork(); //這里使用了doOnNext,因此DisposableObserver的onNext要等到該方法執行完才會回調。
            }

        });
        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();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCompositeDisposable.clear();
    }
}

startSimplePolling對應于固定時延輪詢:


startAdvancePolling對應于變長時延輪詢:

三、示例解析

下面,就讓我們一起來分析一下上面這兩個例子中涉及到的知識點。

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

對于固定時延輪詢的需求,采用的是intervalRange的方式來實現,它是一個創建型操作符,該Observable第一次先發射一個特定的數據,之后間隔一段時間再發送一次,它是intervalrange的結合體,這兩個操作符的原理圖為:

interval 原理圖

range 原理圖

該操作符的優勢在于:

  • interval相比,它可以指定第一個發送數據項的時延、指定發送數據項的個數。
  • range相比,它可以指定兩項數據之間發送的時延。

intervalRange的接收參數的含義為:

  • start:發送數據的起始值,為Long型。
  • count:總共發送多少項數據。
  • initialDelay:發送第一個數據項時的起始時延。
  • period:兩項數據之間的間隔時間。
  • TimeUnit:時間單位。

在輪詢操作中一般會進行一些耗時的網絡請求,因此我們選擇在doOnNext進行處理,它會在下游的onNext方法被回調之前調用,但是它的運行線程可以通過subscribeOn指定,下游的運行線程再通過observerOn切換會主線程,通過打印對應的線程ID可以驗證結果。

當要求的數據項都發送完畢之后,最后會回調onComplete方法。

3.2 repeatWhen 實現變長時延輪詢

3.2.1 使用 repeatWhen 實現重訂閱

之所以可以通過repeatWhen來實現輪詢,是因為它為我們提供了重訂閱的功能,而重訂閱有兩點要素:

  • 上游告訴我們一次訂閱已經完成,這就需要上游回調onComplete函數。
  • 我們告訴上游是否需要重訂閱,通過repeatWhenFunction函數所返回的Observable確定,如果該Observable發送了onComplete或者onError則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。

其原理圖如下所示:

repeatWhen 原理圖

repeatWhen的難點在于如何定義它的Function參數:

  • Function的輸入是一個Observable<Object>,輸出是一個泛型ObservableSource<?>
  • 如果輸出的Observable發送了onComplete或者onError則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。也就是說,它 僅僅是作為一個是否要觸發重訂閱的通知onNext發送的是什么數據并不重要。
  • 對于每一次訂閱的數據流 Function 函數只會回調一次,并且是在onComplete的時候觸發,它不會收到任何的onNext事件。
  • Function函數中,必須對輸入的 Observable<Object>進行處理,這里我們使用的是flatMap操作符接收上游的數據,對于flatMap的解釋,大家可以參考 RxJava2 實戰知識梳理(4) - 結合 Retrofit 請求新聞資訊

而當我們不需要重訂閱時,有兩種方式:

  • 返回Observable.empty(),發送onComplete消息,但是DisposableObserver并不會回調onComplete
  • 返回Observable.error(new Throwable("Polling work finished"))DisposableObserveronError會被回調,并接受傳過去的錯誤信息。

3.2.2 使用 Timer 實現兩次訂閱之間的時延

以上就是對于repeatWhen的解釋,與repeatWhen相類似的還有retryWhen操作符,這個我們在下一篇文章中再介紹,接下來,我們看一下如何實現兩次事件的時延。

前面我們分析過,重訂閱觸發的時間是在返回的ObservableSource發送了onNext事件之后,那么我們通過該ObservableSource延遲發送一個事件就可以實現相應的需求,這里使用的是time操作符,它的原理圖如下所示,也就是,在訂閱完成后,等待指定的時間它才會發送消息。

timer 原理圖

3.2.3 使用 doOnComplete 完成輪詢的耗時操作

由于在訂閱完成時會發送onComplete消息,那么我們就可以在doOnComplete中進行輪詢所要進行的具體操作,它所運行的線程通過subscribeOn指定。


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容