前言
年底了,工作也剛穩(wěn)定下來,新環(huán)境新氣象。
接到個需求,要做token的刷新,直接開搞
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝(一)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之文件下載(二)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之文件上傳(三)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之常見問題(四)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之?dāng)帱c下載(五)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之?dāng)?shù)據(jù)預(yù)處理(六)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之多Url(七)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之Token的刷新(八)
方案一 Interceptor
首先,想到的便是Interceptor攔截器,大概思路就是在攔截器中解析接口返回的json,判斷狀態(tài)碼,然后同步調(diào)用接口刷新token,之后再繼續(xù)調(diào)用原本請求。
- 1.定義刷新token的接口
/**
* 刷新token
*
* @param map map
* @return Call
*/
@FormUrlEncoded
@POST("zhxy-auth/oauth/token")
Call<HashMap<String, String>> refreshToken(@FieldMap HashMap<String, String> map);
- 2.解析接口返回的數(shù)據(jù),判斷狀態(tài)碼,注意:這里需要同步請求
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<String, String> 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<HashMap<String, String>> 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())
我們來試下結(jié)果
image
初步來看,效果還可以,但是,如果是多個接口同時返回
token過期呢?這時就會調(diào)用多次刷新token接口
方案二 retryWhen
rx 中retryWhen操作符可以在執(zhí)行中出現(xiàn)錯誤時,可以將錯誤傳遞到另外一個Flowable,這時我們就可以在這個Flowable中刷新token,刷新成功后,再繼續(xù)重試原先的流程。
- 1.在
GsonResponseBodyConverter(如果使用fastjson也是同理,具體查看【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之?dāng)?shù)據(jù)預(yù)處理(六))中定義token失效的異常,這里已fastjson為例
/**
* @author ch
* @date 2020/12/21-16:38
* desc 失效 需要登錄
*/
public class TokenInvalidException extends JSONException {
}
public class FastJsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
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<Flowable<Throwable>, Publisher<?>>) throwableFlowable ->
throwableFlowable.flatMap((Function<Throwable, Publisher<?>>) 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));
}
需要注意的是,線程調(diào)度需要在retryWhen之后,不然就會拋出NetworkOnMainThreadException異常
- 3.刷新token
/**
* Refresh the token when the current token is invalid.
*
* @return Observable
*/
private Flowable<?> refreshTokenWhenTokenInvalid() {
// call the refresh token api.
HashMap<String, String> 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<HashMap<String, String>> 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.我們還需要保證同時只有一個接口在調(diào)用刷新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<String, String> 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<HashMap<String, String>> 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);
}
}
}
}
讓我們來嘗試下,可以看到調(diào)用了5次接口,返回了5次token過期,調(diào)用了1次刷新token,原來的5次接口再次重新調(diào)用,返回正常結(jié)果
image
至此,需求暫時完成。