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. InGsonResponseBodyConverter(If usingfastjsonSame 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.retryWhenTo 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 codesynchronized

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.

Your recognition is the motivation for me to keep updating my blog. If it is useful, please give me a thumbs-up. Thank you