RxJava2 錯誤處理詳解
熟悉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生命週期管理庫 ,感謝支援。