最后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>(){
});
纯属小白,文章有不合理的、更好的解决方案欢迎大家指教。