在有心课堂的群里,有网友提出如下场景:
当前开发的App遇到一个问题:当请求某个接口时,由于token已经失效,所以接口会报错。但是产品经理希望app能够马上刷新token,然后重复请求刚才那个接口,这个过程对用户来说是无感的。请求A接口-》服务器返回token过期-》请求token刷新接口-》请求A接口
我们应该是怎么解决这个问题呢?
经过百度搜索到了相关信息,这里总结下。
本文是采用RxJava+Retrofit来实现网络请求封装。
实现原理
利用Observale的retryWhen的方法,识别token过期失效的错误信息,此时发出刷新token请求的代码块,完成之后更新token,这时之前的请求会重新执行,但将它的token更新为最新的。另外通过代理类对所有的请求都进行处理,完成之后,我们只需关注单个API的实现,而不用每个都考虑token过期,大大地实现解耦操作。
App多个请求token失效的处理逻辑
当集成了Retrofit之后,我们app中的网络请求接口则变成了一个个单独的方法,这时我们需要添加一个全局的token错误抛出机制,来避免每个接口都所需要的token验证处理。
token失效错误抛出
在Retrofit中的Builder中,是通过GsonConvertFactory来做json转成model数据处理的,这里我们就需要重新实现一个自己的GsonConvertFactory,这里主要由三个文件GsonConvertFactory,GsonRequestBodyConverter,GsonResponseBodyConverter,它们三个从源码中拿过来新建即可。主要我们重写GsonResponseBodyConverter这个类中的convert的方法,这个方法主要将ResponseBody转换我们需要的Object,这里我们通过拿到我们的token失效的错误信息,然后将其以一个指定的Exception的信息抛出。
GsonConverterFactory代码如下:
修改的地方:修改GsonConverterFactory中,生成GsonResponseBodyConverter的方法:
@Override
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);
}
可以看出我们这里对type类型,做以包装,让其重新生成一个类型为ApiModel的新类型。因为我们在写接口代码的时候,都以真正的类型type来作为返回值的,而不是ApiModel。
GsonResponseBodyConverter的处理它的修改,则是要针对返回结果,做以异常的判断并抛出,主要看其的convert方法:
@Override
public Object convert(ResponseBody value) throws IOException {
try {
ApiModel apiModel = (ApiModel) adapter.fromJson(value.charStream());
if (apiModel.errorCode == ErrorCode.TOKEN_NOT_EXIST) {
throw new TokenNotExistException();
} else if (apiModel.errorCode == ErrorCode.TOKEN_INVALID) {
throw new TokenInvalidException();
} else if (!apiModel.success) {
// TODO: 16/8/21 handle the other error.
return null;
} else if (apiModel.success) {
return apiModel.data;
}
} finally {
value.close();
}
return null;
}
错误抛出
当服务器错误信息的时候,同样也是一个model,不同的是success为false,并且含有error_code的信息。所以我们需要针对model处理的时候,做以判断。主要修改的地方就是retrofit的GsonConvertFactory,这里不再通过gradle引入,直接把其源码中的三个文件添加到咱们的项目中。
首先提及的一下是对统一model的封装,如下:
public class ApiModel {
public boolean success;
@SerializedName('error_code') public int errorCode;
public T data;
}
当正确返回的时候,我们获取到data,直接给上层;当出错的时候,可以针对errorCode的信息,做一些处理,让其走最上层调用的onError方法。
多请求的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 getProxy(Class 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
这里的invoke方法则是我们的重头戏,在其中通过将methoinvoke方法包装在Observable中,并添加retryWhen的方法,在retryWhen方法中,则对我们在GsonResponseBodyConverter中暴露出来的错误,做一判断,然后执行重新获取token的操作,这段代码就很简单了。就不再这里细述了。
还有一个重要的地方就是,当token刷新成功之后,我们将旧的token替换掉呢?笔者查了一下,java8中的method类,已经支持了动态获取方法名称,而之前的Java版本则是不支持的。那这里怎么办呢?通过看retrofit的调用,可以知道retrofit是可以将接口中的方法转换成API请求,并需要封装参数的。那就需要看一下Retrofit是如何实现的呢?最后发现重头戏是在Retrofit对每个方法添加的@interface的注解,通过Method类中的getParameterAnnotations来进行获取,主要的代码实现如下:
/**
* Update the token of the args in the method.
*/
private void updateMethodToken(Method method, Object[] args) {
if (mIsTokenNeedRefresh && !TextUtils.isEmpty(GlobalToken.getToken())) {
Annotation[][] annotationsArray = method.getParameterAnnotations();
Annotation[] annotations;
if (annotationsArray != null && annotationsArray.length > 0) {
for (int i = 0; i < annotationsArray.length; i++) {
annotations = annotationsArray[i];
for (Annotation annotation : annotations) {
if (annotation instanceof Query) {
if (TOKEN.equals(((Query) annotation).value())) {
args[i] = GlobalToken.getToken();
}
}
}
}
}
mIsTokenNeedRefresh = false;
}
}
这里,则遍历我们所使用的token字段,然后将其替换成新的toke
代码验证
最上层的代码调用中,添加了两个按钮:
按钮获取token
token获取成功之后,仅仅更新一下全局的token即可。
按钮正常的请求
这里为了模拟多请求,这里我直接调正常的请求5次:
为了查看输出,另外对Okhttp添加了HttpLoggingInterceptor并设置Body的level输出,用来监测http请求的输出。
一切完成之后,先点击获取token的按钮,等待30秒之后,再点击正常请求按钮。可以看到如下的输出:
文章为作者独立观点,不代表股票交易接口观点