咱们在作客户端的设计实现底层网络架构时候,经常不可避免的一个问题: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: 转载请注明原文连接