【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之Token的刷新(八)

前言

年底了,工作也剛穩(wěn)定下來,新環(huán)境新氣象。

接到個需求,要做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ù)重試原先的流程。

/**
 * @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

至此,需求暫時完成。

你的認(rèn)可,是我堅持更新博客的動力,如果覺得有用,就請點個贊,謝謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容