一、示例
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
第一次先發射一個特定的資料,之後間隔一段時間再傳送一次,它是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
函式。 - 我們告訴上游是否需要重訂閱,通過
repeatWhen
的Function
函式所返回的Observable
確定,如果該Observable
傳送了onComplete
或者onError
則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。
其原理圖如下所示:
repeatWhen
的難點在於如何定義它的Function
引數:
Function
的輸入是一個Observable<Object>
,輸出是一個泛型ObservableSource<?>
。- 如果輸出的
Observable
傳送了onComplete
或者onError
則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。也就是說,它 僅僅是作為一個是否要觸發重訂閱的通知,onNext
傳送的是什麼資料並不重要。 - 對於每一次訂閱的資料流 Function 函式只會回撥一次,並且是在
onComplete
的時候觸發,它不會收到任何的onNext
事件。 - 在
Function
函式中,必須對輸入的 Observable進行處理,這裡我們使用的是flatMap
操作符接收上游的資料,對於flatMap
的解釋,大家可以參考 RxJava2 實戰知識梳理(4) - 結合 Retrofit 請求新聞資訊 。而當我們不需要重訂閱時,有兩種方式:
- 返回
Observable.empty()
,傳送onComplete
訊息,但是DisposableObserver
並不會回撥onComplete
。 - 返回
Observable.error(new Throwable("Polling work finished"))
,DisposableObserver
的onError
會被回撥,並接受傳過去的錯誤資訊。
3.2.2 使用 Timer 實現兩次訂閱之間的時延
以上就是對於
repeatWhen
的解釋,與repeatWhen
相類似的還有retryWhen
操作符,這個我們在下一篇文章中再介紹,接下來,我們看一下如何實現兩次事件的時延。前面我們分析過,重訂閱觸發的時間是在返回的
ObservableSource
傳送了onNext
事件之後,那麼我們通過該ObservableSource
延遲傳送一個事件就可以實現相應的需求,這裡使用的是time
操作符,它的原理圖如下所示,也就是,在訂閱完成後,等待指定的時間它才會傳送訊息。3.2.3 使用 doOnComplete 完成輪詢的耗時操作
由於在訂閱完成時會傳送
onComplete
訊息,那麼我們就可以在doOnComplete
中進行輪詢所要進行的具體操作,它所執行的執行緒通過subscribeOn
指定。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/
- 返回