咱們在作客戶端的設計實現底層網絡架構時候,經常不可避免的一個問題:token的有效驗證,如果token過時,則須要先執行refresh token的操做,如果執行refresh token也無效,則須要用戶再執行登錄的過程當中;而這個refresh token的操做,按理來講,對用戶是不可見的。這樣的話,咱們應該是怎麼解決這個問題呢?java
本文是採用RxJava + Retrofit來實現網絡請求的封裝的,則主要討論這種狀況的實現;通常的寫法,則主要是在回調中,作一些攔截的判斷,這裏就不敘述了。git
再使用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
作的判斷是檢測是否爲NullPointerException
和IllegalArgumentException
,其中前者的拋出是在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
的形式拋出來,否則它繼續執行以前的代碼體,陷入一個死循環。架構
當集成了Retrofit以後,咱們的網絡請求接口則變成了一個個單獨的方法,這時咱們須要添加一個全局的token錯誤拋出,以後還得須要對全部的接口作一個統一的retryWhen
的操做,來避免每一個接口都所須要的token驗證處理。ide
在Retrofit中的Builder中,是經過GsonConvertFactory
來作json轉成model數據處理的,這裏咱們就須要從新實現一個本身的GsonConvertFactory,這裏主要由三個文件GsonConvertFactory
,GsonRequestBodyConverter
,GsonResponseBodyConverter
,它們三個從源碼中拿過來新建便可。主要咱們重寫GsonResponseBodyConverter
這個類中的convert
的方法,這個方法主要將ResponseBody
轉換咱們須要的Object,這裏咱們經過拿到咱們的token失效的錯誤信息,而後將其以一個指定的Exception
的信息拋出。ui
爲全部的請求都添加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: 轉載請註明原文連接