欢迎大家加入QQ群一起讨论: 489873144(android格调小窝)
我的github地址:https://github.com/jeasonlzy
概念
Rxjava的操作符分两种
- 一种是对一条数据链中的数据流进行转换
- 另一种是对整条链本身进行转换
一、第一种
对一条数据链中的数据流进行转换
1. 操作符分析
在讲自定义操作符之前,我们不直接来结论如何实现,我们先分析源码,为什么可以这么做,看懂了原因,当然自己写起来就得心应手,而且不容易出错。
我们先看看rxjava已经提供的操作符是如何实现的,先看最常用的map操作符,接受一个转换函数,重新生成一个OnSubscribe对象,OnSubscribe对象是我们发送事件的逻辑,相当于把我们的事件发送包装了一层
这里面有个OnSubscribeMap对象,看名字就知道是Map操作符的实现,我们看看代码,为了更方便的看清逻辑,我将源码稍微简化,并整理,核心逻辑是一样,如下:
public final class OnSubscribeMap<T, R> implements OnSubscribe<R> {
final Observable<T> source;
final Func1<? super T, ? extends R> transformer;
public OnSubscribeMap(Observable<T> source, Func1<? super T, ? extends R> transformer) {
this.source = source;
this.transformer = transformer;
}
@Override
public void call(final Subscriber<? super R> o) {
Subscriber<T> parent = new Subscriber<T>() {
@Override
public void onNext(T t) {
// 注意看这里,调用了外部的转换函数后得到一个新值,回调给原始的观察者
R mapper = transformer.call(t);
o.onNext(mapper);
}
@Override
public void onCompleted() {
o.onCompleted();
}
@Override
public void onError(Throwable e) {
o.onError(e);
}
};
//最后,用原始的Observable对象订阅新的观察者
source.unsafeSubscribe(parent);
}
}
我们发现原理很简单,用外部传进来的转换函数进行变化,得到一个新的值,继续回调给原始的观察者,最后,用原始的Observable对象订阅新的观察者,这样,一旦这个Observable被订阅,那么一定会先执行到这个生成的parent观察者,然后执行转换函数,将转换后的结果回调给目标观察者。一个map操作符就这么简单的完成了。
2. lift变换
我们看到前面分析的内容,知道这个是rxjava默认提供的操作符,而这个方法是Observable对象的方法,我们要是自定义操作符的话总不能改源码,直接在Observable里面添加方法把。所以这个时候就需要一层抽取,把和自定义相关的内容通过接口的形式让外界传递进来。那么这个接口如何定义呢?我们来观察上面map操作符的源码。
其实我们需要做两件事就可以完成自定义的操作符
1. 根据外界传递的方法或者参数,用原始Subscriber生成一个新的Subscriber对象
2. 使用原始的Observable订阅新的Subscriber
这里面第二步永远是不变的,那么我们需要外界传递的就是第一部分。
所以我们定义一个接口如下:
public interface Func1<T, R> extends Function {
R call(T t);
}
public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>> {
}
上面是源码中的定义方法,接受一个Subscriber<T>
类型的观察者,返回一个Subscriber<R>
类型的观察者,简化后的代码如下,是不是完美的表达第一步想要干的事情,这就是抽象思维,真是伟大!
public interface Operator<R, T> extends Func1<Subscriber<R>, Subscriber<T>> {
Subscriber<R> call(Subscriber<T> subscriber);
}
好,抽象结束,我们总该需要一个地方来完成所有的逻辑吧,这个时候就需要在Observable中定义一个方法了,我们叫做lift
,他需要接受一个Operator
接口,然后返回我们需要的Observable
对象,源码如下:
看着是不是和前面的map逻辑一样,没错,既然转换接口有了,我们就要实现第二步的逻辑了,我们发现定义了一个类,叫做OnSubscribeLift,跟代码看看
发现了什么,是不是很明显的两步,第一步调用接口的转换函数,将老的Subscriber也就是o,转成新的Subscriber对象,也就是st,然后使用原始的Observable对象,也就是parent,订阅了新的Subscriber对象st,其余的都是异常处理,不是核心逻辑。
值得注意的是,在这两步中间多了一个方法,叫onStart,这个也是很多博客中会讲到的,他是Subscriber相比Observer新增加的一个方法,用于在发送事件之前回调做点事情,但是无法切换线程,他永远和事件的订阅保持在同一个线程,这个源码就是最好的答案。
3. 自定义操作符
现在终于有了lift方法,自定义的操作符说到底就是lift方法中的Operator接口的实现。
我们撸起袖子,搓搓手掌,开始实战,就使用lift来实现一个map的全部功能,废话不多说,直接上代码,创建一个类OperatorMapTest,实现Operator接口:
构造函数传递一个转换函数,在call方法中,创建一个新的Subscriber并返回,在每个方法的内部实现自己的转换逻辑。
再看如何使用它呢,如下:
是不是特别简单,突然感觉操作符也就这么回事,要是不知道原理还以为有多么神奇呢,是吧,在想想filter操作符呢,做过滤用的,是不是也可以很简单的实现,我们再来一次,创建一个类OperatorFilterTest,实现Operator接口:
使用如下:
二、第二种
整条链本身进行转换
我们写代码经常有这种需求,需要让网络请求在io线程执行,回调在主线程执行,我们把这个场景叫做一组变换,在很多场景下需要的都是同一组变换,如果我们每个地方都这么写一遍就显得太low了,但是如果定义一个方法,在这个方法中写一份逻辑,这样的话又会让链试调用断开,不够优雅,所以对于这种需求我们可以继续抽象。
我们需要传递一个Observable,然后把这个Observable进行我们需要的变换后,再返回这个变换后的Observable。
这个就是总结出来的需求,怎么做呢?同样,抽象出一个接口,万能的抽象,再次膜拜
public interface Func1<T, R> extends Function {
R call(T t);
}
public interface Transformer<T, R> extends Func1<Observable<T>, Observable<R>> {
}
上面是源码中的定义方法,接受一个Observable<T>
类型的观察序列,返回一个Observable<R>
类型的观察序列,简化后的代码如下,是不是再一次完美的表达了需求。
public interface Transformer<T, R> extends Func1<Observable<T>, Observable<R>> {
Observable<R> call(Observable<T> observable);
}
接口抽象后,下一步就是实现逻辑,源码定义了一个compose
方法,看源码如下:
简单的就一行代码,直接调用接口的call方法,并返回,没了!!!
这么简单的话我们快实战试试,定义一个类叫TransformerSchedule,如下
使用如下:
看完这个感觉更简单了,当然我只是举个例子可以做线程切换,其实你完全可以写很复杂的业务逻辑,这里面逻辑有共性,就用一个Transformer去实现,理解了原理,怎么高兴怎么来。
三、总结
rxjava仅仅核心库的操作符目前就多达200多个,这么多的操作符基本满足了绝大部分的需求,详细每个操作符具体的应用场景可以参考本系列第二篇文章,当然你有特殊的需求,现有的无法满足你,那就需要自定义了。还是那句话,理解原理,理解思想,再复杂再神奇的功能也能用很简单的代码实现。
思考题?
我们在创建一个Observable的时候,会创建一个匿名内部类,并实现call方法,这个方法里面传递了一个参数Subscriber,那么这个Subscriber是哪里来的,他是如何创建的?
以上问题就是在使用操作符的过程中,关于事件的通知顺序与回调顺序的一个难点,搞清楚这个问题后,对后续分析RxJava的线程调度原理有很大帮助,可以自己思考下,如果有难度,可以结合下面的图片看源码,也能找到答案。
以下图片是博客何时夕RxJava源代码剖析中,关于事件流程的一个分析,我这里借用一下,感谢作者。