RxJava+Retrofit实现全局过期token自动刷新的实践

       最近项目中遇到一个需求,token过期的自动处理。我们项目中网络请求模块使用了rxJava+Retrofit的组合,相信看到这个文章的人都对此比较了解了,不多赘述。

       看到token过期处理首先想到的是在请求回调中获取到token过期的信息,然后进行刷新操作,但是由于项目中使用到的网络请求接口众多,而且大多数接口都有可能会出现token过期的情况,如此处理就显得比较麻烦了。

       百度之,发现了使用代理来处理的方法,见大神博客:http://alighters.com/blog/2016/08/22/rxjava-plus-retrofitshi-xian-zhi-demo/?utm_source=tuicool&utm_medium=referral ,本篇文章主要讲一讲我在实践中遇到的问题以及解决的方案。


Token自动刷新实现

1.实现思想 

 与alighters的思想相同,摘录如下:利用 Observale 的 retryWhen 的方法,识别 token 过期失效的错误信息,此时发出刷新 token 请求的代码块,完成之后更新 token,这时之前的请求会重新执行,但将它的 token 更新为最新的。另外通过代理类对所有的请求都进行处理,完成之后,我们只需关注单个 API 的实现,而不用每个都考虑 token 过期,大大地实现解耦操作。 

2.token过期处理相关类


token过期处理主要用到如下4个类

     GsonConverterFactory               //在Retrofit.builder().addConverterFactory中使用,直接拷贝原有的 retrofit2.converter.gson.GsonConverterFactory到项目目录下即可
     GsonRequestBodyConverter     //同上,和retrofit中的一样
     GsonResponseBodyConverter  //主要的过期异常抛出代码在其convert()方法中
     ProxyHandler                             //代理类,token刷新代码都在这个类中

3.错误抛出

首先提及我们项目中统一Response的model封装:

public class MResponse implements Serializable {
    public String responseCode;//结果码

    public T responseData;

    public String responseToken; 

}
当正确返回数据时,responseCode为success,当出现token过期时 responseCode为token_timeout.

alighters在其demo对 GsonConverterFactory类中的responseBodyConverter方法中的Type做了包装,其包装代码如下:

public Converter responseBodyConverter(final Type type, Annotation[] annotations, Retrofit retrofit) {
  Type newType = new ParameterizedType() {
      @Override
      public Type[] getActualTypeArguments() {
          return new Type[] { type };
      }

      @Override
      public Type getOwnerType() {
          return null;
      }

      @Override
      public Type getRawType() {
          return ApiModel.class;
      }
  };
  TypeAdapter adapter = gson.getAdapter(TypeToken.get(newType));
  return new GsonResponseBodyConverter<>(adapter);
}

而在实践中发现这样子包装 会造成一些问题 ,本着尽量少改源码的思想,我这里 没有做这些包装 ,维持原样,即:

 public Converter responseBodyConverter(final Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
  }
这样也避免了Type的类型锁死为我们的Model封装。


接下来就到了真正抛出token_timeout异常的地方了,在GsonResponseBodyConverter中,直接看代码:

public Object convert(ResponseBody value) throws IOException {
        try {
            JsonReader jsonReader = gson.newJsonReader(value.charStream());
            Object obj = adapter.read(jsonReader);
            
            if (obj instanceof MResponse) {//token过期则抛出异常
                if (((MResponse) obj).responseCode.equals("token_timeout")) {
                    throw new IllegalArgumentException("token_timeout");//异常类型可以自己定义
                }
            }
            return obj;
        } finally {
            value.close();
        }
    }
使用instanceof使得响应的Model封装不再固定为MResponse,如果有其他类型可以加上else if ,扩展性更好一些。


4.添加代理

添加代理的方式请参照alighters的文章,这里不再描述,贴上一段ProxyHandler中invoke()方法的代码:

public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final InvokeArguments invokeArguments = new InvokeArguments();//变量集合类,避免使用ProxyHandler的成员变量。
        return Observable.just(null).flatMap(new Func1>() {
            @Override
            public Observable call(Object o) {
                try {
                    try {
                        if (invokeArguments.isTokenNeedRefresh) {//需要更新的时候更新方法里的token参数
                            updateMethodToken(invokeArguments, args);
                        }
                        return (Observable) method.invoke(mProxyObject, args);
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }).retryWhen(new Func1, Observable>() {
            @Override
            public Observable call(Observable observable) {
                return observable.flatMap(new Func1>() {
                    @Override
                    public Observable call(Throwable throwable) {
                        if (throwable instanceof IllegalArgumentException 
					&& throwable.getMessage().equals("token_timeout")) {//可自定义过期异常
                            //token_过期处理
                            return refreshTokenWhenTokenInvalid(invokeArguments);//获取新的token
                        }
                        return Observable.error(throwable);//使用error避免进入死循环
                    }
                });
            }
        });
    }


refreshTokenWhenTokenInvalid代码:

private Observable refreshTokenWhenTokenInvalid(final InvokeArguments invokeArguments) {
        synchronized (ProxyHandler.class) {
            invokeArguments.refreshTokenError = null;
            // call the refresh token api.
            ApiWrapper.getInstance().refreshToken()
                   .subscribe(new Subsciber() {
                        @Override
                        public void onNext(MResponse model) {
                            if (model != null) {
                                invokeArguments.isTokenNeedRefresh = true;
                                tokenChangedTime = new Date().getTime();
				GlobalToken.updateToken(model.responseToken);//此处使用自己的token存放方式
                                System.out.println("Refresh token success, time = " + tokenChangedTime);
                            }
                        }

                        @Override
                        public void onError(Throwable e) {
                            invokeArguments.refreshTokenError = e;
                        }
                    });
            if (invokeArguments.refreshTokenError != null) {
                return Observable.error(invokeArguments.refreshTokenError);
            } else {
                return Observable.just(true);
            }
        }
    }


updateMethodToken方法:

 private void updateMethodToken(InvokeArguments invokeArguments, Object[] args) {
        if (invokeArguments.isTokenNeedRefresh && !TextUtils.isEmpty(your new token)) {
            for (int i = 0, len = args.length; i < len; i++) {
                if (args[i] instanceof RequestParams) {
                    ((RequestParams) args[i]).requestToken = your new token;
                }
            }
            invokeArguments.isTokenNeedRefresh = false;
        }
    }



至此全局的token自动刷新功能就完成了,只需用Proxy.newProxyIntance生成的ApiService类来调用请求接口即可实现token过期自动刷新了。为测试可以使用Okhttp的Intercepter来拦截请求,使用Mock的数据来测试,不过要注意mock的数据如果responseCode一直是token_timeout会导致一直循环retry,可以添加 刷新token次数限制来避免。



后记:做完token刷新,本地测试ok之后 兴冲冲的使用服务端的数据来测试,结果居然失败了,罪魁祸首是什么?请看下集,gson解析同一位置不同类型json数据。



你可能感兴趣的:(Android)