1. 程式人生 > >使用RxJava的retryWhen操作符實現token過期自動重新整理

使用RxJava的retryWhen操作符實現token過期自動重新整理

1.問題描述

我們的專案中請求與登入相關介面時需要帶上sessionId這個引數,當發現token過期的時候就需要走重新整理token的介面,獲取最新的token,然後再重新進行請求。

如果專案中是用OkHttp網路框架的話,那麼可以使用Authenticator或者Interceptor來實現,可以參考這篇文章 http://www.jianshu.com/p/62ab11ddacc8。我剛開始的時候也是使用的Interceptor實現的,攔截請求->檢測是否過期->過期則更新token->重新發送請求,但是我發現我的介面回撥中的response仍然是token更新之前的!我已經確保了新的請求攜帶的是最新的token了啊,折騰了好久都沒搞定,所以最後就使用了retryWhen來實現。

2.說明

首先要說明我們專案中資料的封裝方式

返回的json格式

{
    "errorCode" : ...   
    "errorMessage" : ...
    ....
}

errorCode和errorMessage是所有請求都會返回的欄位,我們就是根據errorCode的值來判斷token是否過期,當然你也可能是用狀態碼或者其它方式,差別只是判斷token過期的方法不同而已。

所以我們的封裝方式就是有一個MobileResponse基類,包含errorCode和errorMessage兩個欄位,然後具體的請求繼承MobileResponse。

public class MobileResponse {
    private String errorCode;
    private String errorMessage;
    ....
}

3.程式碼實現

Api介面

// 需要使用token的請求
@POST("updateMobile")
Observable<MobileResponse> updateMobile(@Body UpdateMobileRequest updateMobileRequest);

// 重新整理token的請求
@POST("login")
Observable<LoginResponse> login(@Body LoginRequest request);

呼叫

public void updateMobile(Observer<MobileResponse> observer, UpdateMobileRequest updateMobileRequest) {
    Observable.just(null)
            // 這裡呼叫flatMap方法,主要是需要在這裡設定token,這樣當更新了token之後再次訂閱時,token也是最新的了
            .flatMap(new Function<Object, Observable<MobileResponse>>() {
                @Override
                public Observable<MobileResponse> apply(Object object) throws Exception {
                    // 設定token(我這裡token,session意思是一樣的)
                    updateMobileRequest.setSessionId(UserInfo.getInstance().getSession());
                    // apiService是我上面Api介面的一個例項
                    return apiService.updateMobile(updateMobileRequest);
                }
            })
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .flatMap(new Function<MobileResponse, Observable<? extends MobileResponse>>() {

                @Override
                public Observable<? extends MobileResponse> apply(@NonNull MobileResponse response) throws Exception {
                    // 判斷token是否過期
                    if (ErrorCode.ERROR_NOAUTH.equals(response.getErrorCode())) {
                        return Observable.error(new TokenExpiredException());
                    }
                    return Observable.just(response);
                }
            })
            .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
                    return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                        @Override
                        public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                            if (throwable instanceof TokenExpiredException) {
                                // 如果上面檢測到token過期就會進入到這裡
                                // 然後下面的方法就是更新token
                                return apiService.login(HttpMethods.this.buildLoginRequest())
                                        .subscribeOn(Schedulers.io())
                                        .observeOn(AndroidSchedulers.mainThread())
                                        .unsubscribeOn(Schedulers.io())
                                        .doOnNext(loginResponse -> {
                                            // 儲存最新的token
                                            // 這裡更新完成後就會進行重訂閱,從Observable.just(null)重新開始走。
                                            UserInfo.getInstance().setSession(AndroidApplication.getInstance(), loginResponse.getSessionId());
                                        });
                            } 
                            // 如果是其他錯誤則會呼叫到observer的onError方法中
                            return Observable.error(throwable);
                        }
                    });
                }
            })
            .subscribe(observer);
}



Observer<MobileResponse> observer = new Observer<MobileResponse>() {
        @Override
        public void onSubscribe(@NonNull Disposable d) {
        }

        @Override
        public void onNext(@NonNull MobileResponse response) {
        }

        @Override
        public void onError(@NonNull Throwable e) {
        }

        @Override
        public void onComplete() {
        }
    };

    UpdateMobileRequest request = new UpdateMobileRequest();
    設定引數...
    updateMobile(observer, request);

TokenExpiredException

public class TokenExpiredException extends Exception {
    // 什麼都不用做,當然上面使用TokenExpiredException的地方也可以用其它不會與網路請求產生的異常衝突的異常
}

4.總結

如果資料封裝形式不同的話一些地方就需要改動,這裡也沒有加重試次數的限制。在使用retryWhen的時候我也是找了一大堆文章,雖然沒有完全吻合的但是最後摸索摸索也就做了出來。上面的程式碼在我的專案中是進行過封裝的,這裡為了演示所以把封裝的東西全部省去了。

學習無止境,自己的語言表達和組織能力還是很弱,必須要加把勁了。