【Android架构】基于MVP模式的Retrofit2+RXjava封装之Token的刷新(八)

前言

年底了,工作也刚稳定下来,新环境新气象。

接到个需求,要做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())

我们来试下结果

image

初步来看,效果还可以,但是,如果是多个接口同时返回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次接口再次重新调用,返回正常结果


image

至此,需求暂时完成。

你的认可,是我坚持更新博客的动力,如果觉得有用,就请点个赞,谢谢

你可能感兴趣的:(【Android架构】基于MVP模式的Retrofit2+RXjava封装之Token的刷新(八))