一、前言
1.1 應用背景
在網路請求時,有時候會出現需要進行重試的情況,重試的時候,有以下幾點需要注意:
- 限制重試的次數
- 根據錯誤型別,判斷是否要重試
- 根據錯誤型別,等待特定的時間之後再去重試
我們先來看一下目前的一些網路框架是怎麼做的?通過分析Volley
的原始碼,可以從BasicNetwork
的程式碼中看到,它是將網路請求的程式碼都放在一個無限的while(true)
迴圈當中,如果發生了異常,會在其中的catch
語句中進行處理,如果需要繼續重試,那麼就吞掉這個異常,並將重試次數加1
,這樣就會進入下一次的while(true)
迴圈去訪問網路;如果不需要重試,那麼就丟擲這個異常,退出這個無限迴圈。也就是實現了前面兩點需求。
下面我們就來演示如何通過RxJava2
來輕鬆實現上面的三點需求,通過這篇文章,我們將學習retryWhen
操作符的具體用法,retryWhen
和repeatWhen
經常被大家用來比較,如果對repeatWhen
感興趣的同學可以閱讀上一篇文章 RxJava2 實戰知識梳理(5) - 簡單及進階的輪詢操作。
2.2 示例程式碼
在下面的例子中,我們一共發起了五次請求,也就是subscribe
中的程式碼,其中前四次請求都呼叫onError
方法通知下游請求失敗,同時帶上了自定義的錯誤資訊wait_short
和wait_long
,第五次才返回正確的資料。
當我們收到錯誤之後,會根據錯誤的型別確定重試的時間,同時,我們還儲存了當前重試的次數,避免無限次的重試請求。如果需要重試,那麼通過Timer
操作符延時指定的時間,否則返回Observable.error(Throwable)
放棄重試。
public class RetryActivity extends AppCompatActivity {
private static final String TAG = RetryActivity.class.getSimpleName();
private static final String MSG_WAIT_SHORT = "wait_short";
private static final String MSG_WAIT_LONG = "wait_long";
private static final String[] MSG_ARRAY = new String[] {
MSG_WAIT_SHORT,
MSG_WAIT_SHORT,
MSG_WAIT_LONG,
MSG_WAIT_LONG
};
private TextView mTvRetryWhen;
private CompositeDisposable mCompositeDisposable;
private int mMsgIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_retry);
mTvRetryWhen = (TextView) findViewById(R.id.tv_retry_when);
mTvRetryWhen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startRetryRequest();
}
});
mCompositeDisposable = new CompositeDisposable();
}
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);
}
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();
}
}
}
複製程式碼
上述程式碼的執行結果為,紅框中的間隔就是每次等待重試的時間:
二、示例解析
2.1 retryWhen 介紹
retryWhen
的原理圖如下所示:
retryWhen
提供了 重訂閱 的功能,對於retryWhen
來說,它的重訂閱觸發有兩點要素:
- 上游通知
retryWhen
本次訂閱流已經完成,詢問其是否需要重訂閱,該詢問是以onError
事件觸發的。 retryWhen
根據onError
的型別,決定是否需要重訂閱,它通過返回一個ObservableSource<?>
來通知,如果該ObservableSource
返回onComplete/onError
,那麼不會觸發重訂閱;如果傳送onNext
,那麼會觸發重訂閱。
實現retryWhen
的關鍵在於如何定義它的Function
引數:
Function
的輸入是一個Observable<Throwable>
,輸出是一個泛型ObservableSource<?>
。如果我們接收Observable<Throwable>
傳送的訊息,那麼就可以得到上游傳送的錯誤型別,並根據該型別進行響應的處理。- 如果輸出的
Observable
傳送了onComplete
或者onError
則表示不需要重訂閱,結束整個流程;否則觸發重訂閱的操作。也就是說,它 僅僅是作為一個是否要觸發重訂閱的通知,onNext
傳送的是什麼資料並不重要。 - 對於每一次訂閱的資料流 Function 函式只會回撥一次,並且是在
onError(Throwable throwable)
的時候觸發,它不會收到任何的onNext
事件。 - 在
Function
函式中,必須對輸入的 Observable進行處理,這裡我們使用的是flatMap
操作符接收上游的資料,對於flatMap
的解釋,大家可以參考 RxJava2 實戰知識梳理(4) - 結合 Retrofit 請求新聞資訊 。2.2 retryWhen 和 repeatWhen 對比
在 RxJava2 實戰知識梳理(5) - 簡單及進階的輪詢操作 中我們已經對
可以看到,repeatWhen
進行了介紹,讓我們再來看一下它的原理圖:retryWhen
和repeatWhen
最大的不同就是:retryWhen
是收到onError
後觸發是否要重訂閱的詢問,而repeatWhen
是通過onComplete
觸發。2.3 根據 Throwable 的型別選擇響應的重試策略
由於上游可以通過
onError(Throwable throwable)
中的異常通知retryWhen
,那麼我們就可以根據異常的型別來決定重試的策略。就像我們在上面例子中做的那樣,我們通過
flatMap
操作符獲取到異常的型別,然後根據異常的型別選擇動態地決定延遲重試的時間,再用Timer
操作符實現延遲重試;當然,對於一些異常,我們可以直接選擇不重試,即直接返回Observable.empty
或者Observable.error(Throwable throwable)
。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/