熟悉RxJava的知道,onError跟onComplete是互斥的,出現其中一個,觀察者與被觀察者的關係就被中斷(以下簡稱:管道中斷),觀察者就永遠不會收到來自被觀察者發出的事件。
然後有些情況下,出現了錯誤,我們希望可以進行一些補救措施,例如:
- 由於網路原因或者其他原因,Http請求失敗了,這個時候我們希望進行重試,又或者去讀取本地的快取資料
- 在使用RxJava的組合操作符進行Http併發請求時,我們希望介面之間互不影響,即A介面出現錯誤不會影響B介面的正常流程,反之一樣
現實開發中,可能有更多的場景需要對錯誤進行補救,所以RxJava為我們提供了兩大類進行錯誤處理,分別是Catch和Retry,前者在出現錯誤時補救,後者在出現錯誤時重試,接下來,分別對它們進行講解
注:Catch和Retry只能捕獲上游事件的異常
Catch
Catch操作符共有5個,分別是:
onErrorReturnItem(final T item) //內部呼叫第二個方法
onErrorReturn(Function function) //遇到錯誤,用預設資料項替代
onErrorResumeNext(final ObservableSource<? extends T> next) //內部呼叫第四個方法
onErrorResumeNext(Function resumeFunction ) //遇到錯誤,開始發射新的Observable的資料序列
onExceptionResumeNext(final ObservableSource<? extends T> next) //內部原理與第四個相同,僅有一個引數不同
複製程式碼
雖然有5個操作符,但是實際上就只有3個,再準確點說就只有2個,為什麼這麼說呢,因為第1個操作符內部呼叫的就是第2個,而第3個操作符內部呼叫是第4個操作符,所以說只有3個,那為什麼準確點說只有2個呢,因為第5個操作符,內部原理同第四個,僅僅有一個引數傳的不一樣,接下來我們分別講解。
onErrorReturnItem
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null; //返回null,即出現錯誤
}
})
.onErrorReturnItem(100) //出現錯誤時,用一個預設的資料項將其替代
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
// 列印 100 隨後會立即回撥onComplete
}
});
複製程式碼
我們再來看看onErrorReturnItem
內部實現
public final Observable<T> onErrorReturnItem(final T item) {
ObjectHelper.requireNonNull(item, "item is null");
//將item封裝成Function物件,並呼叫onErrorReturn方法
return onErrorReturn(Functions.justFunction(item));
}
複製程式碼
onErrorReturn
內部原始碼較為簡單,這裡不做講解,接下來看看onErrorReturn
如何使用
onErrorReturn
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null; //返回null,即出現錯誤
}
})
.onErrorReturn(new Function<Throwable, Integer>() {
@Override
public Integer apply(Throwable throwable) throws Exception {
//出現錯誤時,用一個預設的資料項將其替代,這裡根據不同的錯誤返回不同的資料項
return 100;
}
})
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
// 列印 100 隨後會立即回撥onComplete
}
});
複製程式碼
看到上面程式碼可以明白,onErrorReturn
的作用就是在出現錯誤的時候,用一個預設的資料項將錯誤替代,並立刻回撥onComplete。
onErrorResumeNext(final ObservableSource next)
public final Observable<T> onErrorResumeNext(final ObservableSource<? extends T> next) {
ObjectHelper.requireNonNull(next, "next is null");
//可以看到這裡將Observable物件封裝成Function物件,並呼叫onErrorResumeNext方法
return onErrorResumeNext(Functions.justFunction(next));
}
複製程式碼
看原始碼知道onErrorResumeNext(final ObservableSource next)
內部呼叫了onErrorResumeNext(Function resumeFunction )
故這裡不再講解
onErrorResumeNext(Function resumeFunction )
onErrorResumeNext
的作用就是在遇到錯誤時開始發射第二個Observable的資料序列,看程式碼
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return null; //返回null,即出現錯誤
}
})
.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends Integer>>() {
@Override
public ObservableSource<? extends Integer> apply(Throwable throwable) throws Exception {
//出現錯誤時開始發射新的Observable的資料序列
return Observable.just(1, 2, 3);
}
})
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
// 列印 1、2、3 隨後會立即回撥onComplete
}
});
複製程式碼
onExceptionResumeNext
onExceptionResumeNext
跟onErrorResumeNext
作用相同,都是在遇到錯誤時開始發射第二個Observable的資料序列,不同的是,如果onError收到的Throwable不是一個Exception,它會將錯誤傳遞給觀察者的onError方法,不會使用備用的Observable,即它只能捕獲Exception異常,這一點,我們可以在ObservableOnErrorNext$OnErrorNextObserver
類中原始碼看到
//使用onExceptionResumeNext操作符時,allowFatal為true
//使用onErrorResumeNext操作都是,allowFatal為false
if (allowFatal && !(t instanceof Exception)) {
//非Exception異常,直接交給觀察者的onError方法
actual.onError(t);
return;
}
複製程式碼
以上就是Catch操作符的介紹,處理原理無非就兩種,第一種用一個預設的資料項替代錯誤,第二種在遇到錯誤時開始發射一個新的Observable的資料序列,Catch操作符就講解到這,如需要知道具體業務場景,可以看這裡HttpSender 介紹篇之多請求序列與並行(五)
Retry
Retry顧名思義就是在出現錯誤的時候進行重試,共有7個操作符,如下
retry() //無條件,重試無數次
retry(long times) //無條件,重試times次
retry(Predicate predicate) //根據條件,重試無數次
retryUntil(final BooleanSupplier stop) //根據條件,重試無數次
retry(long times, Predicate predicate) //根據條件,重試times次
retry(BiPredicate predicate) //功能與上一個一樣,實現不同
retryWhen(final Function handler) //可以實現延遲重試n次
複製程式碼
前4個操作符內部都呼叫第五個retry(long times, Predicate predicate)
(需要注意的是retryUntil
操作符,只有介面裡的方法返回false時,才會重試),所以我們直接從第五個開始
retry(long times, Predicate predicate)
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//這裡會執行n+1次,其中n為重試次數(如果重試條件為true的話)
return null; //返回null,即出現錯誤
}
})
.retry(3, new Predicate<Throwable>() {//重試3次
@Override
public boolean test(Throwable throwable) throws Exception {
//true 代表需要重試,可根據throwable物件返回是否需要重試
return true;
}
})
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
//重試成功,走這裡
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//重試n次後,依然出現錯誤,直接會走到這裡
}
});
複製程式碼
上面註釋很詳情,這裡不再講解。
retry(BiPredicate predicate)
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//這裡會執行n+1次,其中n為重試次數(如果重試條件為true的話)
return null; //返回null,即出現錯誤
}
})
.retry(new BiPredicate<Integer, Throwable>() {
@Override
public boolean test(Integer times, Throwable throwable) throws Exception {
//times 為嘗試次數,即第幾次嘗試
return times <= 3; //只允許嘗試3次
}
})
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
//重試成功,走這裡
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//重試n次後,依然出現錯誤,直接會走到這裡
}
});
複製程式碼
retry(BiPredicate predicate)
跟retry(long times, Predicate predicate)
功能上是一樣的,只是實現不一樣而已。註釋很詳情,也不再講解。
retryWhen(final Function handler)
先來看看官網的描述:retryWhen
將onError
中的Throwable
傳遞給一個函式,這個函式產生另一個Observable
,retryWhen
觀察它的結果再決定是不是要重新訂閱原始的Observable
。如果這個Observable
發射了一項資料,它就重新訂閱,如果這個Observable
發射的是onError
通知,它就將這個通知傳遞給觀察者然後終止。
這段話的大致意思就是,如果RxJava內部傳過來的Observable
(retryWhen
方法傳入的介面,通過介面方法傳過來的)發射了一項資料,即發射onNext事件,就會重新訂閱原始的Observable
,如果發射的是onError
事件,它就將這個事件傳遞給觀察者然後終止。
那麼,retryWhen
有什麼作用呢,它的主要作用出現錯誤時,重新訂閱,即重試,它跟之前的retry
操作符最大的區別就是,它可以延遲重試,例如,我們有這樣一個需求,需要在遇到錯誤是,隔3秒重試一次,最多重試3次,先來看看程式碼
Disposable disposable = Observable
.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Log.d("LJX", "call");
//這裡會執行n+1次,其中n為重試次數
return null; //返回null,即出現錯誤
}
})
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Observable<Throwable> attempts) throws Exception {
//注:這裡需要根據RxJava傳遞過來的Observable物件發射事件,不能直接返回一個新的Observable,否則無效
return attempts.flatMap(new Function<Throwable, ObservableSource<?>>() {
private final int maxRetries = 3; //最多重試三次
private final int retryDelayMillis = 3; //隔3秒重試一次
private int retryCount; //當前重試次數
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
Log.d("LJX", "apply retryCount=" + retryCount);
//每次遇到錯誤,這裡都會回撥一次
if (++retryCount <= maxRetries) { //最多重試三次
return Observable.timer(retryDelayMillis, TimeUnit.SECONDS);
}
return Observable.error(throwable); //第四次還錯,就直接發射onError事件
}
});
}
})
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
//重試成功,走這裡
Log.d("LJX", "onNext ");
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
//重試n次後,依然出現錯誤,直接會走到這裡
Log.d("LJX", "onError");
}
});
複製程式碼
日誌列印
2019-01-29 17:18:19.764 11179-11179/com.example.httpsender D/LJX: call
2019-01-29 17:18:19.764 11179-11179/com.example.httpsender D/LJX: apply retryCount=0
2019-01-29 17:18:22.771 11179-11229/com.example.httpsender D/LJX: call
2019-01-29 17:18:22.772 11179-11229/com.example.httpsender D/LJX: apply retryCount=1
2019-01-29 17:18:25.775 11179-11231/com.example.httpsender D/LJX: call
2019-01-29 17:18:25.775 11179-11231/com.example.httpsender D/LJX: apply retryCount=2
2019-01-29 17:18:28.779 11179-11242/com.example.httpsender D/LJX: call
2019-01-29 17:18:28.779 11179-11242/com.example.httpsender D/LJX: apply retryCount=3
2019-01-29 17:18:28.781 11179-11242/com.example.httpsender D/LJX: onError
複製程式碼
到這也許有讀者會問,我可以不使用Observable.timer
操作符嗎?可以的,這裡可以使用Observable.intervalRange
操作符替代,可以根據自己的業務需求返回一個Observable
物件,例如使用Observable.just
操作符傳送多個資料項,內部會進行過濾,只有發射的第一個資料項才有效。
好了catch
和retry
兩大類錯誤處理操作符已介紹完畢,如有疑問,請留言,我會第一時間作答。
題外話
如果想在Activity/Fragment的生命週期對RxJava做自動管理,防止記憶體洩漏,可檢視我的另一片文章。Android RxLife 一款輕量級別的RxJava生命週期管理庫,感謝支援。