Rxjava+Retrofit實現全局過時token自動刷新

咱們在作客戶端的設計實現底層網絡架構時候,經常不可避免的一個問題:token的有效驗證,如果token過時,則須要先執行refresh token的操做,如果執行refresh token也無效,則須要用戶再執行登錄的過程當中;而這個refresh token的操做,按理來講,對用戶是不可見的。這樣的話,咱們應該是怎麼解決這個問題呢?java

本文是採用RxJava + Retrofit來實現網絡請求的封裝的,則主要討論這種狀況的實現;通常的寫法,則主要是在回調中,作一些攔截的判斷,這裏就不敘述了。git

單個請求添加token失效的判斷

再使用Rxjava的時候,針對單個API出錯,再進行重試機制,這裏應該使用的操做符是retryWhen, 經過檢測固定的錯誤信息,而後進行retryWhen中的代碼,執行重試機制。這裏有個很好的例子,就是扔物線寫的RxJavaSamples中提到的非一次token的demo。接下來,主要以其中的demo爲例,提一下retryWhen的用法。github

在Demo中的TokenAdvancedFragment中,可查到以下的代碼:json

Observable.just(null)
  .flatMap(new Func1<Object, Observable<FakeThing>>() {
      @Override
      public Observable<FakeThing> call(Object o) {
      return cachedFakeToken.token == null
      ? Observable.<FakeThing>error(new NullPointerException("Token is null!"))
      : fakeApi.getFakeData(cachedFakeToken);
      }
      })
.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
    @Override
    public Observable<?> call(Observable<? extends Throwable> observable) {
    return observable.flatMap(new Func1<Throwable, Observable<?>>() {
        @Override
        public Observable<?> call(Throwable throwable) {
        if (throwable instanceof IllegalArgumentException || throwable instanceof NullPointerException) {
        return fakeApi.getFakeToken("fake_auth_code")
        .doOnNext(new Action1<FakeToken>() {
            @Override
            public void call(FakeToken fakeToken) {
            tokenUpdated = true;
            cachedFakeToken.token = fakeToken.token;
            cachedFakeToken.expired = fakeToken.expired;
            }
            });
        }
        return Observable.just(throwable);
        }
        });
    }
})

代碼中retryWhen執行體中,主要對throwable作的判斷是檢測是否爲NullPointerExceptionIllegalArgumentException,其中前者的拋出是在flatMap的代碼體中,當用戶的token爲空拋出的,而IllegalArgumentException是在何時拋出來的呢?而retryWhen中的代碼體還有fakeApi.getFakeData的調用,看來就是在它之中拋出的,來看一下他的代碼:api

public Observable<FakeThing> getFakeData(FakeToken fakeToken) {
  return Observable.just(fakeToken)
    .map(new Func1<FakeToken, FakeThing>() {
        @Override
        public FakeThing call(FakeToken fakeToken) {
        ...
        if (fakeToken.expired) {
        throw new IllegalArgumentException("Token expired!");
        }

        FakeThing fakeData = new FakeThing();
        fakeData.id = (int) (System.currentTimeMillis() % 1000);
        fakeData.name = "FAKE_USER_" + fakeData.id;
        return fakeData;
        }
        });
}

這裏的代碼示例中能夠看出,當fakeToken失效的時候,則拋出了以前提到的異常。網絡

因此,對token失效的錯誤信息,咱們須要把它以固定的error跑出來,而後在retryWhen中進行處理,針對token失效的錯誤,執行token從新刷新的邏輯,而其餘的錯誤,必須以Observable.error的形式拋出來,否則它繼續執行以前的代碼體,陷入一個死循環。架構

多個請求token失效的處理邏輯

當集成了Retrofit以後,咱們的網絡請求接口則變成了一個個單獨的方法,這時咱們須要添加一個全局的token錯誤拋出,以後還得須要對全部的接口作一個統一的retryWhen的操做,來避免每一個接口都所須要的token驗證處理。ide

token失效錯誤拋出

在Retrofit中的Builder中,是經過GsonConvertFactory來作json轉成model數據處理的,這裏咱們就須要從新實現一個本身的GsonConvertFactory,這裏主要由三個文件GsonConvertFactory,GsonRequestBodyConverter,GsonResponseBodyConverter,它們三個從源碼中拿過來新建便可。主要咱們重寫GsonResponseBodyConverter這個類中的convert的方法,這個方法主要將ResponseBody轉換咱們須要的Object,這裏咱們經過拿到咱們的token失效的錯誤信息,而後將其以一個指定的Exception的信息拋出。ui

多請求的API代理

爲全部的請求都添加Token的錯誤驗證,還要作統一的處理。借鑑Retrofit建立接口的api,咱們也採用代理類,來對Retrofit的API作統一的代理處理。this

  • 創建API代理類

public class ApiServiceProxy {

    Retrofit mRetrofit;

    ProxyHandler mProxyHandler;

    public ApiServiceProxy(Retrofit retrofit, ProxyHandler proxyHandler) {
        mRetrofit = retrofit;
        mProxyHandler = proxyHandler;
    }

    public <T> T getProxy(Class<T> tClass) {
        T t = mRetrofit.create(tClass);
        mProxyHandler.setObject(t);
        return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class<?>[] { tClass }, mProxyHandler);
    }
}

這樣,咱們就須要經過ApiServiceProxy中的getProxy方法來建立API請求。另外,其中的ProxyHandler則是實現InvocationHandler來實現。

public class ProxyHandler implements InvocationHandler {

    private Object mObject;

    public void setObject(Object obj) {
        this.mObject = obj;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        Object result = null;
        result = Observable.just(null)
            .flatMap(new Func1<Object, Observable<?>>() {
                @Override
                public Observable<?> call(Object o) {
                    try {
                        checkTokenValid(method, args);
                        return (Observable<?>) method.invoke(mObject, args);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    return Observable.just(new APIException(-100, "method call error"));
                }
            }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                             @Override
                             public Observable<?> call(Observable<? extends Throwable> observable) {
                                 return observable.
                                     flatMap(new Func1<Throwable, Observable<?>>() {
                                                 @Override
                                                 public Observable<?> call(Throwable throwable) {
                                                     Observable<?> x = checkApiError(throwable);
                                                     if (x != null) return x;
                                                     return Observable.error(throwable);
                                                 }
                                             }

                                     );
                             }
                         }

                , Schedulers.trampoline());
        return result;
        }
  }

這裏的invoke方法則是咱們的重頭戲,在其中經過將method.invoke方法包裝在Observable中,並添加retryWhen的方法,在retryWhen方法中,則對咱們在GsonResponseBodyConverter中暴露出來的錯誤,作一判斷,而後執行從新獲取token的操做,這段代碼就很簡單了。就再也不這裏細述了。

還有一個重要的地方就是,當token刷新成功以後,咱們將舊的token替換掉呢?筆者查了一下,java8中的method類,已經支持了動態獲取方法名稱,而以前的Java版本則是不支持的。那這裏怎麼辦呢?經過看retrofit的調用,能夠知道retrofit是能夠將接口中的方法轉換成API請求,並須要封裝參數的。那就須要看一下Retrofit是如何實現的呢?最後發現重頭戲是在Retrofit對每一個方法添加的@interface的註解,經過Method類中的getParameterAnnotations來進行獲取,主要的代碼實現以下:

Annotation[][] annotationsArray = method.getParameterAnnotations();
Annotation[] annotations = null;
Annotation annotation = null;
if (annotationsArray != null && annotationsArray.length > 0) {
  for (int i = 0; i < annotationsArray.length; i++) {
    annotations = annotationsArray[i];
    for (int j = 0; j < annotations.length; j++) {
      annotation = annotations[j];
      if (annotation instanceof Query) {
        if (ACCESS_TOKEN_KEY.equals(((Query) annotation).value())) {
          args[i] = newToken;
        }
      }
    }
  }
}

這裏,則遍歷咱們所使用的token字段,而後將其替換成新的token.

後記

這裏,整個完整的代碼沒有給出,可是思路走下來仍是很清晰的。筆者這裏的代碼是結合了Dagger2一塊兒來完成的,不過代碼是一步步完善的。另外,咱們仍是有許多點能夠擴展的,例如,將刷新token的代碼變成同步塊,只容許單線程的訪問,這就交給讀者們去一步步完成了。

PS: 轉載請註明原文連接

相關文章
相關標籤/搜索