前言
封装作为面向对象的三大基本特征之一,我们在使用RxJava的时候也必然涉及到封装。
但是Rx是一种数据流链式结构的编程思想,我们在封装时应该不能打断其链式结构。

封装前
如果你有看过我的 使用RxJava优雅的处理服务器返回异常 这篇简书的话,里面有类似下面这样的代码:
_apiService.login(mobile, verifyCode)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> hideLoadingDialog())
.flatMap(result -> {
if (result.status == RESTResult.FAILURE) {
int code = result.code;
// 根据不同code进行不同处理
...
return Observable.error(new ServerException(result.message));
}
return Observable.just(result.data);
})
.subscribe(new Action1<User>() {
@Override
public void call(User user) {
// user对象
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
if (e instanceof ServerException){
Toast.makeText(_context, e.getMessage(), Toast.LENGTH_SHORT).show();
} else{
if (!NetUtil.checkNet(MyApplication.getInstance())) {
Toast.makeText(_context, "网络不可用!", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(_context, "请求失败,请稍后重试", Toast.LENGTH_SHORT).show();
}
}
}
});
上面的代码看起来有点“脏”,一些地方完全可以封装一下,比如:
1、线程的处理,可以进行封装;
2、服务器返回格式一般都是固定的,对服务器返回的状态作处理,可以进行封装;
3、onError里对异常的处理,可以进行封装.
封装方案
1、封装 Rx线程相关
这个我想很多小伙伴都很熟悉,使用compose()操作符!
compose()里接收一个Transformer对象,Transformer继承自Func1<Observable<T>, Observable<R>>,可以通过它将一种类型的Observable转换成另一种类型的Observable。
下面是我的RxSchedulersHelper:
/**
* 处理Rx线程
* Created by YoKey.
*/
public class RxSchedulersHelper {
public static <T> Observable.Transformer<T, T> io_main() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> tObservable) {
return tObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
使用前:
_apiService.login(mobile, verifyCode)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.//省略
使用后:
_apiService.login(mobile, verifyCode)
.compose(RxSchedulersHelper.io_main())
.//省略
以后任何使用
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
的地方都可以使用.compose(RxSchedulersHelper.io_main())
代替啦。
2、封装 处理服务器返回数据
我们把代码里的flatMap()操作符内的内容,作为静态方法提到一个Helper类里,即完成封装。
不过我的做法有点不一样,我还是用了compose+Transformer,在flatMap外包了一层,即:
/**
* Rx处理服务器返回
* Created by YoKey.
*/
public class RxResultHelper {
public static <T> Observable.Transformer<RESTResult<T>, T> handleResult() {
return new Observable.Transformer<RESTResult<T>, T>() {
@Override
public Observable<T> call(Observable<RESTResult<T>> tObservable) {
return tObservable.flatMap(
new Func1<RESTResult<T>, Observable<T>>() {
@Override
public Observable<T> call(RESTResult<T> result) {
if (result.status == RESTResult.SUCCESS) {
return Observable.just(result.getData());
} else if (result.status == RESTResult.SIGN_OUT) {
// 处理被踢出登录情况
return Observable.error(new ReloginException());
} else {
return Observable.error(new ServerException(result.message));
}
return Observable.empty();
}
}
);
}
};
}
}
使用后:
_apiService.login(mobile, verifyCode)
.compose(RxSchedulersHelper.io_main())
.compose(RxResultHelper.handleResult())
.//省略
因为我们服务器的返回的数据格式一般都是一致的,所有我们每个网络请求都可以使用compose(RxResultHelper.handleResult())
来处理服务器返回。
这里我在flatMap外面包了一层compose,原因是我把封装的部分都作为一个Transformer,这样封装的部分都是使用compose操作符,代码看起来更加清晰,当然你也可以直接使用flatMap,即.flatMap(RxResultHelper.handleResult())
(handleResult方法需要更改为flatMap的Func1方法)
3、封装 Subscriber,对异常进行封装
我们已经处理服务器返回,可能有各种各样的异常,比如:
1、网络异常
2、服务器连接异常
3、接口请求参数等异常
我们可以封装一个Subscriber对其进行预处理,让调用者只需关心是Log还是Toast错误消息等行为即可。
/**
* 封装Subscriber
* Created by YoKey.
*/
public abstract class RxSubscriber<T> extends Subscriber<T> {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (e instanceof ServerException) {
// 服务器异常
msg = e.getMessage();
} else if(e instanceof ReloginException){
// 踢出登录
}else if (throwable instanceof UnknownHostException) {
msg = "没有网络...";
} else if (throwable instanceof SocketTimeoutException) {
// 超时
msg = "超时...";
}else{
msg = "请求失败,请稍后重试...");
}
_onError(msg);
}
@Override
public void onNext(T t) {
_onNext(t);
}
public abstract void _onNext(T t);
public abstract void _onError(String msg);
}
使用后:
_apiService.login(mobile, verifyCode)
.//省略
.subscribe(new RxSubscriber<User user>() {
@Override
public void _onNext(User user) {
// 处理user
}
@Override
public void _onError(String msg) {
ToastUtil.showShort(mActivity, msg);
});
这样使用RxSubscriber之后,我们在onNext里只关心对数据的处理,在onError里只关心发生异常该做哪些后续操作即可。
封装后
最后我们再看下经过我们的封装后,文章开头的那块“脏”代码会变成下面这样:
_apiService.login(mobile, verifyCode)
.compose(RxSchedulersHelper.io_main())
.compose(RxResultHelper.handleResult())
.doOnTerminate(() -> hideLoadingDialog())
.subscribe(new RxSubscriber<User user>() {
@Override
public void _onNext(User user) {
// 处理user
}
@Override
public void _onError(String msg) {
ToastUtil.showShort(mActivity, msg);
});
是不是神清气爽了呢?!
当然不仅这里的代码会变得简洁,所有使用Rx处理网络的代码都可以使用上面3个RxHelper类,小伙伴们可随意定制和拓展~
网友评论
在 CallAdapter.Factory#responseBodyConverter的Converter#convert中,应该也可以实现数据的预处理吧!:)
token过期的情况下,作为error处理: Observable.error(new ExpireTokenException())
然后在RxSubscriber里统一处理,再通过回调(比如:callback.onTokenExpire())/或者接口,将该事件传递出去
求助大神一个问题……
在统一异常处理中,我想弹toast或者弹Dialog或者跳转Activity需要context怎么办……
因为token过期的话,是需要跳转到登录页面的。
但是在RxSubscriber中,不能持有context对象。
然后我通过全局的context来调用的话,那么也是有问题的……
因为mvp模式中,presenter不应该持有context对象,否则无法进行junit测试了吧……
我应该怎么做……
http://i2.buimg.com/567571/e145e97b31a36cee.png
但是大神你在Presenter中使用AppHelper的话算不算间接使用到了context,据说presenter中不应该调用任何Android环境,而是通过接口去通知View调用……
看了好几个github上的demo,就是没有这种统一处理的例子=。=
driverApi.login(phone, code)
.compose(RxSchedulersHelper.io_main())
.compose(RxResultHelper.<ResponseData<SmartCar>>handleResult())
第二个泛型一直报错,我的jdk是1.8的,求赐教,谢谢 怎么弄都报错
public class RESTResult<T> {
public static final int FAILURE = 0; // 失败
public static final int SUCCESS = 1; // 成功
private int status; // 返回状态:0 失败 1 成功
private HttpResponseCode code; // 错误码
private String message; // 返回信息
private T data; // 包装的对象
// 其他省略
....
所以写法是这样:
.compose(RxSchedulersHelper.<RESTResult<User>>io_main())
.compose(RxResultHelper.<User>handleResult())
.compose(RxSchedulers.<User>io_main())
.compose(RxResultHelper.<User>handleResult());
第二个泛型一直报错,我的jdk是1.8的,求解释
1 统一处理,如果用clean架构的话,网络访问和网络返回的数据是在data层已经处理好的,那这个统一处理只能封装在data层,但是,就像token失效,用户被踢出登陆的情况怎么在data层写呢?
2 如果不统一处理,特别是网络异常,那只要有网络请求,就需要在所有的请求那里写同一套处理异常的代码。也很不划算。
我现在封装的也是参考一些文章后写出来,但是感觉已经没有了“避免打断链式结构”原意。所以还是不知道究竟该怎么封装好。
...
.doOnSubscribe(() -> showLoading())
. subscribeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> hideLoading())
...
这是普通用法,封装的话还是使用compose+Transformer
if (result.status == RESTResult.SUCCESS) {
//这里为什么不写成Observable.just(result.data)?
return createData(result.data);
} else if (result.status == RESTResult.SIGN_OUT) {
// 处理被踢出登录情况
} else {
return Observable.error(new ServerException(result.message));
}
文中的情况换成just()应该也是没问题的
apiService.login(mobile, verifyCode)
.compose(RxSchedulersHelper.io_main())
.compose(RxResultHelper.<T>handleResult())
这样写的话第二个compose会报错,但是如果和第一个compose换个位置就不报错,不过这样的话貌似代码跑主线程运行了。请问下楼主有demo看下么?
你交换下位置就是在io线程处理了,这个要实际场景了 :)
.compose(RxSchedulersHelper.io_main())
.subscribe(new Action1<RequestInfo<VipDetai>>() {
@Override
public void call(RequestInfo<VipDetai> o) {
}
});
subscribe中报错,不能转成其他类型,只能是object?
.compose(RxSchedulersHelper.<RequestInfo<VipDetai>>io_main())
.subscribe(new Action1<RequestInfo<VipDetai>>() {
@Override
public void call(RequestInfo<VipDetai> o) {
}
});
因为学习rxjava是有成本的,如果项目只有你一个人做,那无所谓,项目往往都有2~3个小伙伴,而小伙伴不一定非常熟悉rxjava,而且人员会流动。这个时候,表层代码应该尽量按照官方调用,降低伙伴的学习成本。
而compose+Transformer,官方的一种用意就是“实现”自己的操作符 可以看这里:
https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators