1. 程式人生 > >巧用RxJava解決網路連線失敗問題及Token失效自動獲取問題

巧用RxJava解決網路連線失敗問題及Token失效自動獲取問題

網路連線失敗的處理

看過最前面那篇文章的應該很清楚retryWhen()是什麼了。
我再來總結一下,retryWhen()的直面意思就是:發生錯誤了,接下來該做什麼。
retryWhen()是RxJava的一種錯誤處理機制,當遇到錯誤時,將錯誤傳遞給另一個Observable來決定是否要重新給訂閱這個Observable

延遲重試
來想象一個場景:使用者用的2G網路或者WiFi訊號不穩定,導致網路經常連線失敗,其實這個時候只要多努力一下就可以連線成功了,如果此時彈出錯誤提示,體驗肯定不好,所以這裡就要用到重試機制

判斷錯誤型別
再模擬一個場景:使用者不是手機訊號不好,而是根本就沒開啟網路,此時還傻傻的重試只是浪費電量而已,所以我們可以加一個判斷,打開了網路才重試,沒有開啟網路就繼續傳送失敗的訊息。

加入重試超時
繼續想,重試也不可能永遠進行,一般都會設定一個重試超時的機制。
想一下,沒有哪個APP只有一個介面地址吧 - -#,如果你用的Retrofit那麼每一個介面返回的Observable都要手動加上上面的重試程式碼,如果是我,我肯定報警了……所以我們必須把剛剛寫的重試程式碼封裝成一個類:

public class TryWhenTransaction implements Func1<Observable<? extends Throwable>, Observable<?>> {

    private static final String TAG = "TokenAdvancedFragment"
; /*** * 重試間隔時間 */ private long mInterval; public TryWhenTransaction(long interval) { mInterval = interval; } private int retryCount = 0; @Override public Observable<?> call(final Observable<? extends Throwable> observable) { return observable.flatMap(new
Func1<Throwable, Observable<?>>() { @Override public Observable<?> call(Throwable throwable) { if (throwable instanceof UnknownHostException) { //若沒開啟網路則停止重試 return Observable.error(throwable); } else if (throwable instanceof NullPointerException) Log.i(TAG, "call: Time:" + new Date(System.currentTimeMillis()) + " thread:" + Thread.currentThread().getName()); //重試三次 if (++retryCount < 3) return Observable.timer(5, TimeUnit.SECONDS); else return Observable.error(new IllegalArgumentException("超過最大次數"));//超過最大次數終止 } }); } }

呼叫程式碼:

public void testMethodA() {
        Observable.just(token).flatMap(new Func1<Integer, Observable<FakeThing>>() {
            @Override
            public Observable<FakeThing> call(Integer o) {
                //若請求資料得不到響應 則返回一個Error 會啟用retry
                Log.i(TAG, "call: first  token invalide is:" + o + " thread:" + Thread.currentThread().getName());
                return Observable.error(new NullPointerException("請求資料失敗"));
            }
        }).retryWhen(
                new TryWhenTransaction(5))
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
            @Override
            public void onCompleted() {
                Log.i(TAG, "onCompleted: ");
            }

            @Override
            public void onError(Throwable e) {
                //這邊可以根據錯誤型別來分別處理
                if (e instanceof IllegalArgumentException) {
                    Log.e(TAG, "onError: ", e);
                }
            }

            @Override
            public void onNext(FakeThing fakeThing) {

            }
        });
    }

日誌:
這裡寫圖片描述

token失效解決方案
使用者長時間不訪問app可能導致token的失效,這時如果繼續訪問app可能會導致使用者重新登入降低體驗,所以token失效自動重新獲取可以增強使用者體驗。
上程式碼:

public void testMethodB() {
        // 使用token進行網路請求
        Observable.just(token).flatMap(new Func1<Integer, Observable<FakeThing>>() {
            @Override
            public Observable<FakeThing> call(Integer o) {
                //若token是1000則為無效token
                if (o == 1000) {
                    Log.i(TAG, "call: first  token invalide is:" + o);
                    return Observable.error(new RuntimeException("TokenError"));
                }
                Log.i(TAG, "call: after retry request token:" + token + "  original o is still:" + o);
                return Observable.just(new FakeThing());
            }
        }).retryWhen(
                new Func1<Observable<? extends Throwable>, Observable<?>>() {
                    @Override
                    public Observable<?> call(final Observable<? extends Throwable> observable) {
                        return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                            @Override
                            public Observable<?> call(Throwable throwable) {
                                if (throwable instanceof RuntimeException) {//如果是token錯誤就開啟重試
                                    //重新請求Token 獲取到token為2000
                                    token = 2000;
                                    Log.i(TAG, "call: retry execute token changed is:" + token);
                                    return observable.just(null).delay(1, TimeUnit.SECONDS);
                                }
                                return Observable.error(throwable);//其他錯誤就不開啟重試
                            }
                        });
                    }
                })
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
            @Override
            public void onCompleted() {
                Log.i(TAG, "onCompleted: ");
            }

            @Override
            public void onError(Throwable e) {
                try {
                    throw e;
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }

            @Override
            public void onNext(FakeThing fakeThing) {

            }
        });
    }

上執行截圖:這裡寫圖片描述
有木有發現新獲取的Token本來應該是2000再重訂閱的時候顯示卻是1000!其實,之所以造成這個的原因是因為對Obserable的工作機制沒有理解透徹導致的。在建立一個Obserable的時候,引數的內容,固定的步驟就已經決定好了。 即使在過程中引數發生了改變,retry的時候,還是使用原來的值去請求的。可以使用HttpLoggingInterceptor驗證這個。
那總是原始值怎麼辦?看下面
我們需要每次重試的時候,產生一個新的Obserable。這時候我們可以藉助defer操作符來實現,每次呼叫都是一個新的Obserable。
把請求介面的Obserable用Obserable.defer包裹一下即可。

 public void testMethodC() {
        //使用defer重新包裹請求 可以在retry後使用新(重新獲取的)token進行請求
        // 如果不包裹則會使用第一次傳進去的token進行請求
        Observable.defer(new Func0<Observable<Integer>>() {
            @Override
            public Observable<Integer> call() {
                //這裡寫請求 由於token過期或者為null傳回錯誤碼-1

                return Observable.just(token);
            }
        }).flatMap(new Func1<Integer, Observable<FakeThing>>() {
            @Override
            public Observable<FakeThing> call(Integer o) {
                if (o == 1000) {
                    Log.i(TAG, "call: first  token invalide is:" + o);
                    return Observable.error(new RuntimeException("TokenError"));
                }
                Log.i(TAG, "call: after retry request token:" + token + "  original o is still:" + o);
                return Observable.just(new FakeThing());
            }
        }).retryWhen(
                new Func1<Observable<? extends Throwable>, Observable<?>>() {
                    @Override
                    public Observable<?> call(final Observable<? extends Throwable> observable) {
                        return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                            @Override
                            public Observable<?> call(Throwable throwable) {
                                if (throwable instanceof RuntimeException) {
                                    //重新請求Token 獲取到token為2000
                                    token = 2000;
                                    Log.i(TAG, "call: retry execute token changed is:" + token);
                                    return observable.just(null);
                                }

                                return Observable.error(throwable);
                            }
                        });

                    }
                })
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<FakeThing>() {
            @Override
            public void onCompleted() {
                Log.i(TAG, "onCompleted: ");
            }

            @Override
            public void onError(Throwable e) {
                try {
                    throw e;
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }

            @Override
            public void onNext(FakeThing fakeThing) {

            }
        });
    }

上截圖:
這裡寫圖片描述

至於次數限制就自己加仿造本片第一段程式碼。