一、封装思路来源
在使用第三方开源框架时,有一个原则那就是一般都会将其暴露的 API 进行再次的封装,这样做的目的就是避免该框架 API 的变动,或者随着技术的更新可能有更好的技术去替代时,可以减少大面积的修改调用处。
举个栗子,早期的时侯使用图片加载的框架,我们一般会使用毕加索、ImageLoader 等,而近年来又多了几个更好的框架,例如 Fresco,Glide 等等,如果代码使用的是原始框架的 API ,那么每次换新的图片加载框架,那都是血的代价啊。
本次我们将对时下最流行的响应式编程 RxJava 框架作一次简单的封装,封装需要达到的目标就是,让调用者不需要使用到 RxJava 原生的 API ,而是使用封装之后的 API 。请注意,本次封装只是对一些常见的操作进行封装,并且这种方式具备扩张性。
以下代码是对 RxJava 封装之后的示例代码:
public void loadData() {
//在泛型中指定需要请求的数据类型是String
new SmartDoManager<String>()
.create(new SmartDoManager.EmitterCallback<String>() {
@Override
public void getEmitDatas(List<String> datas) throws Exception {
//数据源发送数据
datas.add("a");
datas.add("b");
datas.add("c");
datas.add("d");
}
})
.transform(new SmartDoManager.Transform<String, String>() {
@Override
public String apply(String s) {
return s + s;
}
})
.switchThread()//切换线程
.deliveryResult(new SmartDoManager.ConsumerCallback<String>() {
@Override
public void onReceive(String data) {
//接受到数据源发送的数据
Log.e("zeal", data);
}
@Override
public void onError(Throwable t) {
//异常
Log.e("zeal", t.toString());
}
});
}
注意本文是建立在已对 RxJava 做了了解并想要对其封装的同学,在开始封装之前先对 RxJava 几个知识点先做一下了解:
Observable : 被观察者,用于发送事件的。
Observer : 观察者,当 Observable 发送事件时,它可以收到事件。
订阅关系 : 在 RxJava 中只有 Observable 被 Observer 订阅了,才能发送事件。
线程切换 : RxJava 是一个异步的事件处理框架,因此对线程这一块的支持还是很到位的。在切换线程的角度来看,主要分为请求数据所在的线程(一般会在子线程,因为这里一般是做网络请求,或者数据库操作等耗时操作),处理数据所在的线程(这里一般会在主线程,因为这里一般会做与 UI 相关的更新操作)。
转换数据 : 何为转换数据呢?其实就是将某中类型的数据进行操作之后转化为另外一种类型的数据,例如网络请求回来的数据是 Json 类型的字符串,那么可以通过类型转换操作符,转换为对应的 Java Bean 类型。
二、开始封装
以上 5 点在项目中会经常使用到,因此本项目就为这 5 点展开封装:
- 封装 Observable 对象
这里是对新建一个类 SmartDoManager ,因为我们整个过程都是需要建立在 Observable 之上的,因此我们使用 SmartDoManager
对 Observable 对象做了包装,这里的泛型 T 需要跟 Observable 需要同步,表示需要请求数据的类型。
public class SmartDoManager<T> {
private Observable<T> mObservable;
}
- 创建 Observable 的时机
(1)示例代码:
new SmartDoManager<String>()
.create(new SmartDoManager.EmitterCallback<String>() {
@Override
public void getEmitDatas(List<String> datas) throws Exception {
//数据源发送数据
datas.add("a");
datas.add("b");
datas.add("c");
datas.add("d");
}
})
(2)分析:
对外暴露 create 方法,其实内部就是创建 Observable 对象,上面我们说过 Observable 就是事件源,是发送事件的地方,因此我们需要在外部提供需要发送的事件,这里我使用一个 EmitterCallback 的方式来从外界获取需要发送的数据。
这里有一点需要注意的是,在获取需要发送的事件是通过 callback.getEmitDatas(datas) ,而不是在通过 List callback.getEmitDatas() 返回一个 List 集合,这是有原因的,因为在 Observable 事件发送过程中,若是发送中途出现了问题,那么观察者还是可以接收到前面正常发送的数据,具体看下图。
(3)分析一下如何获取事件源和发送数据:
通过 callback.getEmitDatas(datas) 接口回调的方式,从外界获取需要发送的事件。在获取数据过程若出现异常,那么在 catch 中会将该异常赋值给 t 对象,因为即使出现异常,那么还是需要保证订阅者能收到先前正常发送的数据的,那么如何将这正常的数据发送出去呢?
上面问题的解决方案是在 finally 块中:
第一步:先通过 for 循环遍历 datas 集合调用 onNext() 发送正常事件
for (int i = 0; i < datas.size(); i++) {
Log.e("zeal", "onNext:" + datas.get(i));
//发送数据,要是 onNext 异常会直接走 onError 不能 catch ;
e.onNext(datas.get(i));
}
第二步:在获取发送事件时若是出现异常,对象 t 是不为 null ,它标记是否需要发送 e.onError(t) 事件。注意这里有一个 e.isDisposed() 方法的判断,这是什么呢?在源码声明中可以知道 isDisposed() 返回 true 表示 e.onNext(datas.get(i)) 是正常的,若是出现异常情况那么 isDisposed() 就会返回 false 。所以根据上面的 t 和 isDisposed() 联合就可以判断发送事件的过程中是否出现异常。
finally {
for (int i = 0; i < datas.size(); i++) {
Log.e("zeal", "onNext:" + datas.get(i));
e.onNext(datas.get(i));//发送数据,要是 onNext 异常会直接走 onError 不能 catch ;
}
if (!e.isDisposed() && t != null) {//判断在 onNext 过程中是否出现异常,若是 onError 的话,isDisposed 返回 true
e.onError(t);
} else {
e.onComplete();
}
}
create 方法的完整代码
public SmartDoManager<T> create(final EmitterCallback<T> callback) {/创建 Obsrvable
Observable<T> observable = Observable.create(new ObservableOnSubscribe<T>() {
@Override
public void subscribe(ObservableEmitter<T> e) {
List<T> datas = new ArrayList<T>();
Throwable t = null;
try {
callback.getEmitDatas(datas);//获取数据
} catch (Exception exception) {
exception.printStackTrace();
t = exception;//标记该异常
} finally {
for (int i = 0; i < datas.size(); i++) {
Log.e("zeal", "onNext:" + datas.get(i));
e.onNext(datas.get(i));//发送数据,要是 onNext 异常会直接走 onError 不能 catch ;
}
if (!e.isDisposed() && t != null) {//判断在 onNext 过程中是否出现异常,若是 onError 的话,isDisposed 返回 true
e.onError(t);
} else {
e.onComplete();
}
}
}
});
return new SmartDoManager<T>(observable);
}
public static interface EmitterCallback<T> {
void getEmitDatas(List<T> datas) throws Exception;
}
总结事件发送:事件源 Observable 发送事件已经封装好了,从上面代码可以看到,是通过 EmitterCallback 接口获取需要发送的事件,然后遍历集合发送事件,同时处理了在 getEmitDatas(datas) 和 onNext(datas.get(i)) 出现的异常情况。
- 订阅事件
事件发送已经封装完毕了,那么接下来就是封装订阅者订阅事件这一块了。
在 Observable 中建立订阅关系是通过 subscribe(Observer) 方法实现,具体细节不展示了。下面就是我们对 RxJava 订阅的封装:
示例代码:
deliveryResult(new SmartDoManager.ConsumerCallback<String>() {
@Override
public void onReceive(String data) {
//接受到数据源发送的数据
Log.e("zeal", data);
}
@Override
public void onError(Throwable t) {
//异常
Log.e("zeal", t.toString());
}
});
封装实现:
public void deliveryResult(final ConsumerCallback<T> callback) {
mObservable.subscribe(new Observer<T>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(T t) {
callback.onReceive(t);
}
@Override
public void onError(Throwable e) {
callback.onError(e);
}
@Override
public void onComplete() {
callback.onComplete();
}
});
}
public static class ConsumerCallback<T> {
public void onReceive(T data) {
}
public void onError(Throwable t) {
}
public void onComplete() {
}
}
从上面 deliveryResult 方法可以看出,主要是对 subscribe 方法进行的封装,而具体在 onNext() , onError() 等方法的回调,我是通过一个 ConsumerCallback 抽象类去实现,在这里解释一下为什么不使用抽象类,因为在子类中,可以选择性去实现需要的功能。
订阅总结:订阅事件的实现还是比较简单的,只是将 Observer 接受到的事件在通过 ConsumerCallback 回调给调用者即可。
- 切换线程的封装
示例代码:
.switchThread()//切换线程
我们一般在 RxJava 中的请求数据一般会在子线程中,而处理请求回来的数据一般会在主线程中,那么这就涉及到了线程在数据请求和数据处理之间的切换操作。
现在我们就是要对下面两个 API 进行封装:
subscribeOn(Schedulers.io())
observeOn(AndroidSchedulers.mainThread())
下面代码就是对上面两个 API 的封装,代码都很简单,主要功能就是上面所说的,请求数据会在子线程中去执行,处理数据会在主线程去执行,这种处理方式在 Android 中是非常常见的,例如 AsyncTask 。
public SmartDoManager<T> switchThread() {
return new SmartDoManager<T>(mObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()));
}
public SmartDoManager(Observable<T> observable) {
this.mObservable = observable;
}
主要注意的是这里为什么要将线程切换返回的 Observable 对象传到 SmartDoManager 的构造方法中呢?
这里解释一下原因:在 RxJava 源码中我们很容易看到每次对 Observable 的操作,都会返回一个新的 Observable 对象,这个新的 Observable 对象会对旧的 Observable 对象进行一次封装。我们这里的是设计思路也是参考 Observable 的实现的,每次对 SmartDoManager 中的 mObservable 操作之后都将新的 Observable 对象传递到 SmartDoManager 构造中,这样就拥有一个新的 Observable 对象了。
- 转换数据类型
示例代码:将事件源发送的字符串 s 通过 apply 方法转化为 s+s ,然后返回。
例如事件源发送过来的数据是 s = “hello”,那么通过 apply 方法转换后就变成 “hellohello” 了。
.transform(new SmartDoManager.Transform<String, String>() {
@Override
public String apply(String s) {
return s + s;
}
})
在 RxJava 中数据类型的转换在实际应用很广泛,例如服务端返回的 mobile api 一般都会遵守某种约定,例如下面这种Response 的 json 格式:
public class Response<T>{
private int code;
private String msg;
private T data;
//getter/setter 方法
}
那么如何将 json 格式的数据转化为上面的 Response 对象呢?这就需要进行类型转换了,也就是 String 转换为 Response 对象。
在 RxJava 中使用 map 的方式将接受到的事件类型转化为另外一种类型,具体封装如下:
在这里的 T 表示创建 SmartDoManager 指定的类型,也就是需要请求的数据类型,Q 表示需要将类型 T 转换为类型 Q 。而按照上面几个封装的思想,这里也是提供接口的方式进行回调,因为数据如何转换是需要调用者知道的,框架只负责接口的调用。在这里通过 Transform
public <Q> SmartDoManager<Q> transform(final Transform<T, Q> callback) {
Observable<Q> observable = mObservable.map(new Function<T, Q>() {
@Override
public Q apply(T t) throws Exception {
return callback.apply(t);
}
});
return new SmartDoManager<Q>(observable);
}
- 合并 compose 操作
除了数据的类型转换外,那么多个请求结果的数据转换 RxJava 也是支持的,对应的操作符就是 zip操作符。在该方法中是将两个 Observable 对象执行的结果通过某一种转换规则进行数据转换。
示例代码:
.compose(new SmartDoManager<String>()//
.create(new SmartDoManager.EmitterCallback() {
@Override
public void getEmitDatas(List datas) throws Exception {
datas.add("1");
datas.add("2");
datas.add("3");
}
}), new SmartDoManager<String>()
.create(new SmartDoManager.EmitterCallback() {
@Override
public void getEmitDatas(List datas) throws Exception {
datas.add("4");
datas.add("5");
datas.add("6");
}
}),
new SmartDoManager.ApplyCallback<String, String, String>() {
@Override
public String apply(String s, String s2) {
return s + s2;
}
})
具体代码封装:
public <H, Q> SmartDoManager<T> compose(SmartDoManager<H> s1, SmartDoManager<Q> s2, final ApplyCallback<H, Q, T> callback) {
Observable<T> observable = Observable.zip(s1.getObservable(), s2.getObservable(), new BiFunction<H, Q, T>() {
@Override
public T apply(H h, Q q) throws Exception {
return callback.apply(h, q);
}
});
return new SmartDoManager<T>(observable);
}
public static interface ApplyCallback<H, Q, T> {
T apply(H h, Q q);
}
上面代码就是对 zip 操作符的封装,代码都很简单,跟上面 map 操作符的封装一样,都是通过接口把具体的转换规则暴露给外界,这个接口就是 ApplyCallback 。
在这里以上几点就封装完毕了,具体使用也很简单,示例代码就在最开始贴出的代码。
三、总结: 在这个 RxJava 的简单封装过程可以学到很多东西:
1、泛型的应用,这个很重要,因为是链式编程,因此对类型的约束是必要的;
2、在 SmartDoManager 中添加接受 Observable 对象的构造 ,华丽地实现数据类型的类型转换;
3、接口回调的正确处理,渐低用户对原始 Observable Api 的使用;
4、在整个封装过程几乎每一个方法返回的是当前类对象 SmartDoManager ,这样就实现了 Observable 的链式编程。
5、获取需要发送的事件是通过 callback.getEmitDatas(datas) 的方式而不是 datas = callback.getEmitDatas(); 的原因的思考。
6、所有的接口回调都不是继承的方式,而是自己定义规则;
“`