RxJava2 錯誤處理詳解

不怕天黑發表於2019-04-19

熟悉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

onExceptionResumeNextonErrorResumeNext作用相同,都是在遇到錯誤時開始發射第二個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)

先來看看官網的描述:retryWhenonError中的Throwable傳遞給一個函式,這個函式產生另一個ObservableretryWhen觀察它的結果再決定是不是要重新訂閱原始的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操作符傳送多個資料項,內部會進行過濾,只有發射的第一個資料項才有效。

好了catchretry兩大類錯誤處理操作符已介紹完畢,如有疑問,請留言,我會第一時間作答。

題外話

如果想在Activity/Fragment的生命週期對RxJava做自動管理,防止記憶體洩漏,可檢視我的另一片文章。Android RxLife 一款輕量級別的RxJava生命週期管理庫,感謝支援。

相關文章