您的位置:首页 > 编程语言 > Java开发

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

2016-12-06 10:46 696 查看
       最近项目中遇到一个需求,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<T> implements Serializable {
public String responseCode;//结果码

public T responseData;

public String responseToken;

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

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

public Converter<ResponseBody, ?> 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<ResponseBody, ?> 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<Object, Observable<?>>() {
@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;

4000
}
}).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.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<MResponse>() {
@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数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: