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

前言

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

接到个需求,要做token的刷新,直接开搞

方案一 Interceptor

首先,想到的便是Interceptor拦截器,大概思路就是在拦截器中解析接口返回的json,判断状态码,然后同步调用接口刷新token,之后再继续调用原本请求。

  • 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.解析接口返回的数据,判断状态码,注意:这里需要同步请求
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())

我们来试下结果

image

初步来看,效果还可以,但是,如果是多个接口同时返回token过期呢?这时就会调用多次刷新token接口

方案二 retryWhen

rx 中retryWhen操作符可以在执行中出现错误时,可以将错误传递到另外一个Flowable,这时我们就可以在这个Flowable中刷新token,刷新成功后,再继续重试原先的流程。

/**
 * @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));
    }

需要注意的是,线程调度需要在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.我们还需要保证同时只有一个接口在调用刷新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);
                }
            }
        }
    }

让我们来尝试下,可以看到调用了5次接口,返回了5次token过期,调用了1次刷新token,原来的5次接口再次重新调用,返回正常结果


image

至此,需求暂时完成。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容