preface
It’s the end of the year, and the job has just stabilized.
Received a demand, to do token refresh, directly open
- Retrofit2+RXjava encapsulation based on MVP Mode
- Retrofit2+RXjava package file download based on MVP mode
- Retrofit2+RXjava package file upload based on MVP mode
- Common Problems with Retrofit2+RXjava encapsulation based on MVP Mode (iv)
- MVP mode Retrofit2+RXjava encapsulation breakpoint Download (5)
- Retrofit2+RXjava Encapsulation based on MVP Mode data preprocessing (6)
- 【Android architecture 】 Retrofit2+RXjava encapsulation based on MVP mode
- MVP mode Retrofit2+RXjava package Token refresh (8)
Plan aInterceptor
In the Interceptor Interceptor, you parse the JSON returned by the interface, determine the status code, and then call the interface to refresh the token, and then continue to call the original request.
- 1. Define the interface for refreshing the token
/** * Refresh token **@param map map
* @return Call
*/
@FormUrlEncoded
@POST("zhxy-auth/oauth/token")
Call<HashMap<String, String>> refreshToken(@FieldMap HashMap<String, String> map);
Copy the code
- 2. Parse the data returned by the interface and determine the status code. Note that the request needs to be synchronized
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)) {
// The token needs to be refreshed
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
// Disable the proxy
.proxy(Proxy.NO_PROXY)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiRetrofit.BASE_SERVER_URL)
// Add a custom parser
/ / support 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());
// Synchronize the request
retrofit2.Response<HashMap<String, String>> tokenResponse = apiServer.refreshToken(map).execute();
if(tokenResponse.body() ! =null) {
/ / save the token
TokenCommon.saveToken(tokenResponse.body().get("token"));
TokenCommon.saveRefreshToken(tokenResponse.body().get("refreshToken"));
/ / add a token
Request newRequest = request.newBuilder()
.addHeader("Authorization"."Bearer " + TokenCommon.getToken())
.build();
response.close();
try {
return chain.proceed(newRequest);
} catch(IOException e) { e.printStackTrace(); }}}}returnresponse; }}Copy the code
- 3. Add interceptors
.addInterceptor(new TokenInterceptor())
So let’s try it out
At first glance, the effect is ok, but if multiple interfaces return at the same timeToken expired
? The multiple refresh Token interface is invoked
Scheme 2retryWhen
The retryWhen operator in RX allows us to pass errors during execution to another Flowable where we can refresh the token and then retry the original process.
- 1. In
GsonResponseBodyConverter
(If usingfastjson
Same thing, look at itRetrofit2+RXjava Encapsulation based on MVP Mode data preprocessing (6)). Fastjson is used as an example
/ * * *@author ch
* @date2020/12/21-16:38 * desc Login is required */
public class TokenInvalidException extends JSONException {}Copy the code
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) {
// Returning null neither succeeds nor fails
return (T) "";
}
if (data instanceof String) {
return (T) data;
}
return JSON.parseObject(object.getString("data"), type, Feature.SupportNonPublicField);
} else if ("A0232".equals(code)) {
// The token expires and needs to be refreshed
/ / remove the token
TokenCommon.clearToken();
throw new TokenTimeOutException();
} else if ("A0231".equals(code) || "A0233".equals(code) || "A0234".equals(code)) {
// Need to log in again
throw new TokenInvalidException();
}
throw new RuntimeException(msg);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally{ value.close(); buffer.close(); }}}Copy the code
- In 2.
retryWhen
To catch the exception
/**
* 添加
*
* @param flowable flowable
* @param subscriber subscriber
*/
protected void addDisposable(Flowable
flowable, BaseSubscriber subscriber) {
if (compositeDisposable == null) {
compositeDisposable = newCompositeDisposable(); } compositeDisposable.add(flowable.retryWhen((Function<Flowable<Throwable>, Publisher<? >>) throwableFlowable -> throwableFlowable.flatMap((Function<Throwable, Publisher<? >>) throwable -> {if (throwable instanceof TokenInvalidException) {
// The token is invalid and you need to log in again
} else if (throwable instanceof TokenTimeOutException) {
/ / token expired
return refreshTokenWhenTokenInvalid();
}
return Flowable.error(throwable);
})).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(subscriber));
}
Copy the code
It is important to note that the thread scheduling needs after retryWhen, otherwise will be thrown NetworkOnMainThreadException anomalies
- 3. Refresh token
/**
* Refresh the token when the current token is invalid.
*
* @return Observable
*/
privateFlowable<? > 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());
// Synchronize the request
retrofit2.Response<HashMap<String, String>> tokenResponse = null;
try {
tokenResponse = apiServer.refreshToken(map).execute();
} catch (IOException e) {
e.printStackTrace();
}
if(tokenResponse ! =null&& tokenResponse.body() ! =null) {
/ / save the token
}
return Flowable.just(true);
}
Copy the code
- 4. We also need to ensure that only one interface is calling the refresh token interface at the same time, so we add to this code
synchronized
The complete code is as follows
/**
* Refresh the token when the current token is invalid.
*
* @return Observable
*/
privateFlowable<? > 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());
// Synchronize the request
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();
/ / save the token
TokenCommon.saveToken(tokenResponse.body().get("token"));
TokenCommon.saveRefreshToken(tokenResponse.body().get("refreshToken"));
}
return Flowable.just(true); }}}Copy the code
Let’s try it. We can see that the interface is called 5 times, the token expiration is returned 5 times, the refresh token is called 1 time, and the original 5 times the interface is called again with normal resultsAt this point, the requirements are complete for the time being.