转载

Rxjava+Retrofit实现网络代理

我们在做客户端的设计实现底层网络架构时候,常常不可避免的一个问题:token的有效验证,若是token过期,则需要先执行refresh token的操作,若是执行refresh token也无效,则需要用户再执行登陆的过程中;而这个refresh token的操作,按理来说,对用户是不可见的。这样的话,我们应该是怎么解决这个问题呢?

本文是采用RxJava + Retrofit来实现网络请求的封装的,则主要讨论这种情况的实现;一般的写法,则主要是在回调中,做一些拦截的判断,这里就不叙述了。

单个请求添加token失效的判断

再使用Rxjava的时候,针对单个API出错,再进行重试机制,这里应该使用的操作符是 retryWhen , 通过检测固定的错误信息,然后进行 retryWhen 中的代码,执行重试机制。这里有个很好的例子,就是 扔物线写的RxJavaSamples 中提到的非一次token的demo。接下来,主要以其中的demo为例,提一下retryWhen的用法。

在Demo中的 TokenAdvancedFragment 中,可查到如下的代码:

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 的调用,看来就是在它之中抛出的,来看一下他的代码:

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验证处理。

token失效错误抛出

在Retrofit中的Builder中,是通过 GsonConvertFactory 来做json转成model数据处理的,这里我们就需要重新实现一个自己的GsonConvertFactory,这里主要由三个文件 GsonConvertFactory , GsonRequestBodyConverter , GsonResponseBodyConverter ,它们三个从源码中拿过来新建即可。主要我们重写 GsonResponseBodyConverter 这个类中的 convert 的方法,这个方法主要将 ResponseBody 转换我们需要的Object,这里我们通过拿到我们的token失效的错误信息,然后将其以一个指定的 Exception 的信息抛出。

多请求的API代理

为所有的请求都添加Token的错误验证,还要做统一的处理。借鉴Retrofit创建接口的api,我们也采用代理类,来对Retrofit的API做统一的代理处理。 + 建立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的代码变成同步块,只允许单线程的访问,这就交给读者们去一步步完成了。

原文  http://alighters.com/blog/2016/05/02/rxjava-plus-retrofitshi-xian-wang-luo-dai-li/
正文到此结束
Loading...