前言
年底了,工作也刚稳定下来,新环境新气象。
接到个需求,要做token的刷新,直接开搞
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装(一)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件下载(二)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件上传(三)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之常见问题(四)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之断点下载(五)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之数据预处理(六)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之多Url(七)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之Token的刷新(八)
方案一 Interceptor
首先,想到的便是Interceptor
拦截器,大概思路就是在拦截器中解析接口返回的json
,判断状态码,然后同步调用接口刷新token
,之后再继续调用原本请求。
- 1.定义刷新token的接口
/**
* 刷新token
*
* @param map map
* @return Call
*/
@FormUrlEncoded
@POST("zhxy-auth/oauth/token")
Call> refreshToken(@FieldMap HashMap map);
- 2.解析接口返回的数据,判断状态码,注意:这里需要同步请求
public class TokenInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (response.body() != null) {
BufferedSource buffer = Okio.buffer(response.body().source());
String jsonString = buffer.readUtf8();
JSONObject object = JSON.parseObject(jsonString);
String code = object.getString("code");
if ("A0230".equals(code)) {
//需要刷新token
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
//禁用代理
.proxy(Proxy.NO_PROXY)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiRetrofit.BASE_SERVER_URL)
//添加自定义的解析器
//支持RxJava2
.addConverterFactory(FastJsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
ApiServer apiServer = retrofit.create(ApiServer.class);
HashMap map = new HashMap<>();
map.put("grant_type", "refresh_token");
map.put("client_id", "zhxy-centre-web");
map.put("client_secret", "123456");
map.put("refresh_token", TokenCommon.getRefreshToken());
//同步请求
retrofit2.Response> tokenResponse = apiServer.refreshToken(map).execute();
if (tokenResponse.body() != null) {
//保存token
TokenCommon.saveToken(tokenResponse.body().get("token"));
TokenCommon.saveRefreshToken(tokenResponse.body().get("refreshToken"));
//添加token
Request newRequest = request.newBuilder()
.addHeader("Authorization", "Bearer " + TokenCommon.getToken())
.build();
response.close();
try {
return chain.proceed(newRequest);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return response;
}
}
- 3.添加拦截器
.addInterceptor(new TokenInterceptor())
我们来试下结果
初步来看,效果还可以,但是,如果是多个接口同时返回
token过期
呢?这时就会调用多次刷新token接口
方案二 retryWhen
rx 中retryWhen
操作符可以在执行中出现错误时,可以将错误传递到另外一个Flowable
,这时我们就可以在这个Flowable
中刷新token,刷新成功后,再继续重试原先的流程。
- 1.在
GsonResponseBodyConverter
(如果使用fastjson
也是同理,具体查看【Android架构】基于MVP模式的Retrofit2+RXjava封装之数据预处理(六))中定义token失效的异常,这里已fastjson为例
/**
* @author ch
* @date 2020/12/21-16:38
* desc 失效 需要登录
*/
public class TokenInvalidException extends JSONException {
}
public class FastJsonResponseBodyConverter implements Converter {
private Type type;
FastJsonResponseBodyConverter(Type type) {
this.type = type;
}
@Override
public T convert(ResponseBody value) throws IOException {
BufferedSource buffer = Okio.buffer(value.source());
String jsonString = buffer.readUtf8();
try {
JSONObject object = JSON.parseObject(jsonString);
String code = object.getString("code");
String msg = object.getString("msg");
if ("00000".equals(code)) {
Object data = object.get("data");
if (null == data) {
//返回null 既不会走成功 也不会走失败
return (T) "";
}
if (data instanceof String) {
return (T) data;
}
return JSON.parseObject(object.getString("data"), type, Feature.SupportNonPublicField);
} else if ("A0232".equals(code)) {
//token 过期 需要刷新
//清除token
TokenCommon.clearToken();
throw new TokenTimeOutException();
} else if ("A0231".equals(code) || "A0233".equals(code) || "A0234".equals(code)) {
//需要重新登录
throw new TokenInvalidException();
}
throw new RuntimeException(msg);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
value.close();
buffer.close();
}
}
}
- 2.在
retryWhen
中捕获该异常
/**
* 添加
*
* @param flowable flowable
* @param subscriber subscriber
*/
protected void addDisposable(Flowable> flowable, BaseSubscriber subscriber) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(flowable.retryWhen((Function, Publisher>>) throwableFlowable ->
throwableFlowable.flatMap((Function>) throwable -> {
if (throwable instanceof TokenInvalidException) {
//token 失效 需要重新登录
} else if (throwable instanceof TokenTimeOutException) {
//token 过期
return refreshTokenWhenTokenInvalid();
}
return Flowable.error(throwable);
})).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(subscriber));
}
需要注意的是,线程调度需要在retryWhen
之后,不然就会抛出NetworkOnMainThreadException
异常
- 3.刷新token
/**
* Refresh the token when the current token is invalid.
*
* @return Observable
*/
private Flowable> refreshTokenWhenTokenInvalid() {
// call the refresh token api.
HashMap map = new HashMap<>();
map.put("grant_type", "refresh_token");
map.put("client_id", "zhxy-centre-web");
map.put("client_secret", "123456");
map.put("refresh_token", TokenCommon.getRefreshToken());
//同步请求
retrofit2.Response> tokenResponse = null;
try {
tokenResponse = apiServer.refreshToken(map).execute();
} catch (IOException e) {
e.printStackTrace();
}
if (tokenResponse != null && tokenResponse.body() != null) {
//保存token
}
if (mRefreshTokenError != null) {
return Flowable.error(mRefreshTokenError);
} else {
return Flowable.just(true);
}
}
- 4.我们还需要保证同时只有一个接口在调用刷新token接口,我们给这段代码加上
synchronized
完整代码如下
/**
* Refresh the token when the current token is invalid.
*
* @return Observable
*/
private Flowable> refreshTokenWhenTokenInvalid() {
synchronized (BasePresenter.class) {
// Have refreshed the token successfully in the valid time.
if (System.currentTimeMillis() - tokenChangedTime < REFRESH_TOKEN_VALID_TIME) {
mIsTokenNeedRefresh = true;
return Flowable.just(true);
} else {
// call the refresh token api.
HashMap map = new HashMap<>();
map.put("grant_type", "refresh_token");
map.put("client_id", "zhxy-centre-web");
map.put("client_secret", "123456");
map.put("refresh_token", TokenCommon.getRefreshToken());
//同步请求
retrofit2.Response> tokenResponse = null;
try {
tokenResponse = apiServer.refreshToken(map).execute();
} catch (IOException e) {
e.printStackTrace();
}
if (tokenResponse != null && tokenResponse.body() != null) {
mIsTokenNeedRefresh = true;
tokenChangedTime = new Date().getTime();
//保存token
TokenCommon.saveToken(tokenResponse.body().get("token"));
TokenCommon.saveRefreshToken(tokenResponse.body().get("refreshToken"));
}
if (mRefreshTokenError != null) {
return Flowable.error(mRefreshTokenError);
} else {
return Flowable.just(true);
}
}
}
}
让我们来尝试下,可以看到调用了5次接口,返回了5次token过期,调用了1次刷新token,原来的5次接口再次重新调用,返回正常结果
至此,需求暂时完成。