基于Rxjava2的token刷新机制

本文介绍了一种在Rxjava2+Retrofit2框架下,利用retryWhen操作符实现自动刷新Token的机制,以解决Token过期问题,提升用户体验。

 

最后1、需求

在操作网络的时候,我们公司有时候需要提交参数“token”,以此判断是否登录过期。而token是登录的时候返回的一个参数。当超过有效期6个小时,此时为了提升用户体验,在访问网络接口时,检测到token失效,往往需要我们先做刷新token的操作,再重新访问该接口。

2、思路

在Rxjava2+Retrofit2的网络框架基础上,通过Rxjava的操作符retryWhen以此来实现刷新token的操作。

公司后台接口返回的数据格式是:

{
    "status":911
    ...//其他字段
}

status返回911时代表token失效。除了status字段是固定的,夹杂着不同的字段,这对bean的封装难度很大。结合retrywhen操作符,换种思路,如果在一开始访问网络获取数据得到的status为911,此时抛出异常,检测到这个异常,在进行刷新操作。

先看代码:

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .client(sOkHttpClient.build())
                //将服务器返回的json字符串转化为对象
                .addConverterFactory(GsonConverterFactory.create())
                //这个是用来决定你的返回值是Observable还是Call。
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

这是我封装的一个RxRetrofit(相信大家都会自己封装~,就不贴完整的了),其中有个方法addConverterFactory();该方法正是将返回的数据转为我们自定义的bean对象。按照刚才的说法,我们需要在封装数据之前就进行数据的检测,不多说,直接自定义GsonConverterFactory。关于自定义gson构造器,涉及到三个类:GsonConverterFactory、GsonResponseBodyConverter、GsonRequestBodyConverter。从字面看就可以理解GsonResponseBodyConverter就是数据返回的解析类。直接看代码:

//跟原生的GsonConverterFactory格式一样,部分内容修改
public class MyGsonConverterFactory extends Converter.Factory {

    private final Gson gson;

    private MyGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static MyGsonConverterFactory create() {
        return create(new Gson());
    }

    public static MyGsonConverterFactory create(Gson gson) {
        return new MyGsonConverterFactory(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new MyGsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new MyGsonRequestBodyConverter<>(gson, adapter);
    }
}
//自定义MyGsonRequestBodyConverter
final class MyGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;

    MyGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter, value);
        jsonWriter.close();
        return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
    }
}
//自定义MyGsonResponseBodyConverter,其中数据解析就是在convert中判断
final class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;
    private static final Charset UTF_8 = Charset.forName("UTF-8");


    MyGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {

        String response = value.string();
        JsonReader jsonReader = null;
        int status = 0;
        try {
            try{
                JSONObject object = new JSONObject(response);
                 status = object.optInt("status");
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (status == 911) {
                throw new TokenExpireException("token已失效");
            }
            //储存数据
            MediaType mediaType = value.contentType();
            Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
            InputStream inputStream = new ByteArrayInputStream(response.getBytes());
            jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, charset));
            return adapter.read(jsonReader);
        }finally {
            value.close();
        }
    }
}

其中最关键的就是MyGsonResponseBodyConverter,需要在convert方法中进行数据的检测。我的做法是获取数据源,解析成JSONObject对象,读取status字段是否为911,检测到911就抛出异常。其中有个注意点(重要的事说三遍):value.string()只读一次,value.string()只读一次,value.string()只读一次。这就要求我们读取后需要把数据给储存起来。

返回RxRetrofit,此时修改内容:

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .client(sOkHttpClient.build())
                //将服务器返回的json字符串转化为对象
                .addConverterFactory(MyGsonConverterFactory.create())
                //这个是用来决定你的返回值是Observable还是Call。
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

到此,只要访问网络,识别到status为911,此时就会抛出异常TokenException。


划重点,以上只是对数据源进行处理,接下来才是Rxjava的施展。通过自定义gson构造器,当检测到数据携带“status”:911,此时会抛出异常。我们只要在retryWhen识别到这个异常即可判断需要我们进行登录操作以此刷新token。

先说个错误的设计:

Observable.just(token)
                .flatMap(new Function<String, ObservableSource<LoginBean>>() {
                    @Override
                    public ObservableSource<LoginBean> apply(String s) throws Exception {
                        return RxRetrofit.createApi(LoginApi.class)
                                .getInfo(s);//这是获取用户个人信息的接口,需要提交token,可能存在token失效问题
                    }
                })
                ...
                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
                return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                        if (throwable instanceof TokenExpireException) {
                           //进行登录,刷新token,并将数据重新发射出去
                            //do something
                            return Observable.just(token);
                        }else {
                            // 如果是其他错误则会调用到observer的onError方法中
                            return Observable.error(throwable);
                        }
                    }
                });
            }
        })
        ...;

一开始我想直接通过.just(token)把token发射出去,在检测到TokenException异常的时候执行登录操作刷新token,执行Observable.just(token)将新的token重新发射出去,结果发现token的数值依旧不变。也就是一开始发射的token,该值之后无法修改(这是我的理解)。根据这个异常做了以下调整:

Observable.just(0)//随便输入一个值,注意:Rxjava2不再允许输入null,否则会报异常
                .flatMap(new Function<String, ObservableSource<LoginBean>>() {
                    @Override
                    public ObservableSource<LoginBean> apply(String s) throws Exception {
                        token = SPUtils.getValue(SPConstants.USER_INFO_TAB, SPConstants._TOKEN, "");//这里我把token储存在文件,读取文件的值
                        return RxRetrofit.createApi(LoginApi.class)
                                .getInfo(token);//这是获取用户个人信息的接口,需要提交token,可能存在token失效问题
                    }
                })
                ...
                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
                return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                        if (throwable instanceof TokenExpireException) {
                           //进行登录,刷新token,储存token
                        //do something
                        SPUtils.putValue(SPConstants.USER_INFO_TAB, SPConstants._TOKEN, bean.get_token());
                            return Observable.just(0);
                        }else {
                            // 如果是其他错误则会调用到observer的onError方法中
                            return Observable.error(throwable);
                        }
                    }
                });
            }
        })
        ...;

按照以上的写法,对部分方法进行封装,就不多说了,直接上代码了。

以下是RxUtil工具类,处理线程和解决登录异常问题:

public class RxUtil {

    /**
     * 线程处理
     */
    public static <T> ObservableTransformer<T, T> rxSchedulersHelper() {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }


    /**
     * 解决登录失效问题
     */
    public static <T> ObservableTransformer<T, T> rxTokenHelper() {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.
                        compose(RxUtil.<T>rxSchedulersHelper())
                       .retryWhen(handleToken());
            }
        };
    }


    /**
     * 解决登录异常的方法
     * @return
     */
    private static Function<Observable<Throwable>, ObservableSource<?>> handleToken(){
        return new Function<Observable<Throwable>, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
                return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                        if (throwable instanceof TokenExpireException) {
                            // 进行登录,刷新token
                            return RxRetrofit.createApi(LoginApi.class).withPwLogin("phone","pw",null,"","")
                                    .compose(RxUtil.<LoginBean>rxSchedulersHelper())
                                    .doOnNext(new Consumer<LoginBean>() {
                                        @Override
                                        public void accept(LoginBean bean) throws Exception {
                                            //do something
                                            SPUtils.putValue(SPConstants.USER_INFO_TAB, SPConstants.LOGIN_TOKEN, "");
                                        }
                                    });
                        }else {
                            // 如果是其他错误则会调用到observer的onError方法中
                            return Observable.error(throwable);
                        }
                    }
                });
            }
        };
    }
}

封装Observer观察者,调用subscribe()就可以更加简洁一些了。

public class BaseObserver<T> implements Observer<T> {
    
    public BaseObserver() {
    }

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onComplete() {

    }

    @Override
    public void onNext(T value) {

    }

    @Override
    public void onError(Throwable e) {

    }
}

直接使用:

Observable.just(0)
                .flatMap(new Function<Integer, ObservableSource<LoginBean>>() {
                    @Override
                    public ObservableSource<LoginBean> apply(Integer integer) throws Exception {
                        //读取token,确保token是最新的
                        token = SPUtils.getValue(SPConstants.USER_INFO_TAB, SPConstants._TOKEN, "");
                        return RxRetrofit.createApi(LoginApi.class).getInfo(token);
                    }
                })
                .compose(RxUtil.rxTokenHelper())
                .subscribe(new BaseObserver<LoginBean>(){
                });

 纯属小白,文章有不合理的、更好的解决方案欢迎大家指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值