给 Android 开发者的 RxJava 详解
WIKI
ReactiveX/RxJava文档中文版
观察者模式与发布/订阅模式区别
总结
1. 从两张图片可以看到,最大的区别是调度的地方。
虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。
2. 两种模式都可以用于松散耦合,改进代码管理和潜在的复用。
Observer是RxJava的观察者,Subscriber 是其子类,另外增加了一些额外功能,应该算是观察者的基本实现
*在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的.
*观察者模式本身的目的就是『后台处理,前台回调』的异步机制,
因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler
*在不指定线程的情况下, RxJava 遵循的是线程不变的原则,
即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。
*所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列
*FuncX 和 ActionX 的区别在 FuncX 包装的是有返回值的方法
* map() 是一对一的转化
*flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。flatMap输出的新的Observable正是我们在Subscriber想要接收的
Observable.from()方法,它接收一个集合作为输入,然后每次输出一个元素给subscriber
*flatMap() 的原理是这样的:
1. 使用传入的事件对象创建一个 Observable 对象;
2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable , 而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。
这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。
*subscribe() 中这句话的 onSubscribe 指的是 Observable 中的 onSubscribe 对象,这个没有问题,但是 lift() 之后的情况就复杂了点。
*当含有 lift() 时:
1.lift() 创建了一个 Observable 后,加上之前的原始 Observable,已经有两个 Observable 了;
2.而同样地,新 Observable 里的新 OnSubscribe 加上之前的原始 Observable 中的原始 OnSubscribe,也就有了两个 OnSubscribe;
3.当用户调用经过 lift() 后的 Observable 的 subscribe() 的时候,使用的是 lift() 所返回的新的 Observable ,于是它所触发的 onSubscribe.call(subscriber),也是用的新 Observable 中的新 OnSubscribe,即在 lift() 中生成的那个 OnSubscribe;
4.而这个新 OnSubscribe 的 call() 方法中的 onSubscribe ,就是指的原始 Observable 中的原始 OnSubscribe ,在这个 call() 方法里,新 OnSubscribe 利用 operator.call(subscriber) 生成了一个新的 Subscriber(Operator 就是在这里,通过自己的 call() 方法将新 Subscriber 和原始 Subscriber 进行关联,并插入自己的『变换』代码以实现变换),然后利用这个新 Subscriber 向原始 Observable 进行订阅。
这样就实现了 lift() 过程,有点像一种代理机制,通过事件拦截和处理实现事件序列的变换。
*精简掉细节的话,也可以这么说:在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。
*observeOn()指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次observeOn()即可。
*不同于observeOn(),subscribeOn()的位置放在哪里都可以,但它是只能调用一次的。
当使用了多个subscribeOn()的时候,只有第一个subscribeOn()起作用。
*subscribeOn()原理图:
observeOn()原理图:
从图中可以看出,subscribeOn()和observeOn()都做了线程切换的工作(图中的 "schedule..." 部位)。不同的是,subscribeOn()的线程切换发生在OnSubscribe中,即在它通知上一级OnSubscribe时,这时事件还没有开始发送,因此subscribeOn()的线程控制可以从事件发出的开端就造成影响;而observeOn()的线程切换则发生在它内建的Subscriber中,即发生在它即将给下一级Subscriber发送事件时,因此observeOn()控制的是它后面的线程。
subscribeOn()指定观察者代码运行的线程,使用observerOn()指定订阅者运行的线程
图中共有 5 处含有对事件的操作。由图中可以看出,①和②两处受第一个subscribeOn()影响,运行在红色线程;③和④处受第一个observeOn()的影响,运行在绿色线程;⑤处受第二个onserveOn()影响,运行在紫色线程;而第二个subscribeOn(),由于在通知过程中线程就被第一个subscribeOn()截断,因此对整个流程并没有任何影响。这里也就回答了前面的问题:当使用了多个subscribeOn()的时候,只有第一个subscribeOn()起作用。
*而与Subscriber.onStart()相对应的,有一个方法Observable.doOnSubscribe()。它和Subscriber.onStart()同样是在subscribe()调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下,doOnSubscribe()执行在subscribe()发生的线程;而如果在doOnSubscribe()之后有subscribeOn()的话,它将执行在离它最近的subscribeOn()所指定的线程。
RxBinding是 Jake Wharton 的一个开源库,它提供了一套在 Android 平台上的基于 RxJava 的 Binding API。所谓 Binding,就是类似设置OnClickListener、设置TextWatcher这样的注册绑定对象的 API。
RxBinding的目的:扩展性
RxBus http://www.jianshu.com/p/ca090f6e2fe2
根据响应式函数编程的概念,Subscribers更应该做的事情是“响应”,响应Observable发出的事件,而不是去修改
当调用Observable.subscribe(),会返回一个Subscription对象。这个对象代表了被观察者和订阅者之间的联系。
RxJava的另外一个好处就是它处理unsubscribing的时候,会停止整个调用链。如果你使用了一串很复杂的操作符,调用unsubscribe将会在他当前执行的地方终止。不需要做任何额外的工作!
生命周期
我把最难的不分留在了最后。如何处理Activity的生命周期?主要就是两个问题:
1.在configuration改变(比如转屏)之后继续之前的Subscription。
比如你使用Retrofit发出了一个REST请求,接着想在listview中展示结果。如果在网络请求的时候用户旋转了屏幕怎么办?你当然想继续刚才的请求,但是怎么搞?
2.Observable持有Context导致的内存泄露
这个问题是因为创建subscription的时候,以某种方式持有了context的引用,尤其是当你和view交互的时候,这太容易发生!如果Observable没有及时结束,内存占用就会越来越大。
不幸的是,没有银弹来解决这两个问题,但是这里有一些指导方案你可以参考。
第一个问题的解决方案就是使用RxJava内置的缓存机制,这样你就可以对同一个Observable对象执行unsubscribe/resubscribe,却不用重复运行得到Observable的代码。cache() (或者 replay())会继续执行网络请求(甚至你调用了unsubscribe也不会停止)。这就是说你可以在Activity重新创建的时候从cache()的返回值中创建一个新的Observable对象。
Java代码
Observable request = service.getUserPhoto(id).cache();
Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));
// ...When the Activity is being recreated...
sub.unsubscribe();
// ...Once the Activity is recreated...
request.subscribe(photo -> handleUserPhoto(photo));
注意,两次sub是使用的同一个缓存的请求。当然在哪里去存储请求的结果还是要你自己来做,和所有其他的生命周期相关的解决方案一延虎,必须在生命周期外的某个地方存储。(retained fragment或者单例等等)。
第二个问题的解决方案就是在生命周期的某个时刻取消订阅。一个很常见的模式就是使用CompositeSubscription来持有所有的Subscriptions,然后在onDestroy()或者onDestroyView()里取消所有的订阅。
Java代码
privateCompositeSubscription mCompositeSubscription
=newCompositeSubscription();
privatevoiddoSomething() {
mCompositeSubscription.add(
AndroidObservable.bindActivity(this, Observable.just("Hello, World!"))
.subscribe(s -> System.out.println(s)));
}
@Override
protectedvoidonDestroy() {
super.onDestroy();
mCompositeSubscription.unsubscribe();
}
你可以在Activity/Fragment的基类里创建一个CompositeSubscription对象,在子类中使用它。
注意! 一旦你调用了 CompositeSubscription.unsubscribe(),这个CompositeSubscription对象就不可用了, 如果你还想使用CompositeSubscription,就必须在创建一个新的对象了。
Subject
Subject主题是可观察者Observable的一个拓展,同时实现了Observer接口,也就是说,通过引入Subject,我们将可观察者和观察者联系起来,这样主要是为了简化,Subject能像观察者那样接受发送给它们的事件,也能像可观察者一样将事件发送给自己的订阅者。Subject能成为RxJava的理想入口,当你有来自Rx外部的事件数据值时,你能将它们推送到一个Subject,把它们转为一个可观察者(被观察者),由此可以作为Rx整个管道连的切入点。这个概念很有函数编程的味道。
Subject有两个参数类型:输入类型和输出类型,这是来自函数编程中纯函数的概念,Subject有许多不同实现,我们看看主要的实现子类:
1. PublishSubject 是最直接主要的Subject实现,当一个事件值被发送给PublishSubject时,它会将这个事件值发送给订阅它的每个订阅者:
public static void main(String[] args) {
PublishSubject< Integer> subject = PublishSubject.create();
subject.onNext(1);
subject.subscribe(System.out::println);
subject.onNext(2);
subject.onNext(3);
subject.onNext(4);
}
这段代码输出一个序列或系列值,包含三个数字: 2 3 4
但是,你会发现数值1并没有被输出,因为它被推送时,subject还没有被订阅为系统输出System.out::println,只有订阅以后我们才能接受到发送给它的数值。这里的subscribe是onNext的一个Function,这个函数有一个整数型参数,但是什么也不返回,一个什么也不返回的Function也称为action。
2. ReplaySubject 是用来缓存所有推送给它的数据值,当有一个新的订阅者,那么就会为这个新的订阅者从头开始播放原来的一系列事件。当再有新的事件来时,所有的订阅者也会接受到的。
ReplaySubject< Integer> s = ReplaySubject.create();
s.subscribe(v -> System.out.println("Early: " + v));
s.onNext(0);
s.onNext(1);
s.subscribe(v -> System.out.println("Late: " + v));
s.onNext(2);
上述代码运行输出:
Early:0
Early:1
Late: 0
Late: 1
Early:2
Late: 2
所有的事件值都被订阅者接受到了,无论它们是否在订阅之前或之后被推送的。请注意,缓存所有数据会受内存大小限制,我们需要限制缓存的大小,ReplaySubject.createWithSize是用来限制缓存大小,而ReplaySubject.createWithTime限制一个对象会被缓存多长时间。
ReplaySubject< Integer> s = ReplaySubject.createWithSize(2);
s.onNext(0);
s.onNext(1);
s.onNext(2);
s.subscribe(v -> System.out.println("Late: " + v));
s.onNext(3);
上述代码输出:
Late: 1
Late: 2
Late: 3
订阅者没有收到数值0的第一个值,因为缓存大小是2,下面是时间限制:
ReplaySubject< Integer> s = ReplaySubject.createWithTime(150, TimeUnit.MILLISECONDS,
Schedulers.immediate());
s.onNext(0);
Thread.sleep(100);
s.onNext(1);
Thread.sleep(100);
s.onNext(2);
s.subscribe(v -> System.out.println("Late: " + v));
s.onNext(3);
输出:
Late: 1
Late: 2
Late: 3
创建ReplaySubject 需要一个Scheduler,但是RxJava会为了并发帮你在Scheduler上节省时间,可见后面的并发编程章节。
3. BehaviorSubject是只记得最后的值,类似于将缓存大小设为1的ReplaySubject,创建时会提供一个初始值,这样能保证被订阅时总是立即有一个值可用。
BehaviorSubject< Integer> s = BehaviorSubject.create();
s.onNext(0);
s.onNext(1);
s.onNext(2);
s.subscribe(v -> System.out.println("Late: " + v));
s.onNext(3);
输出:
Late: 2
Late: 3
下面是展示onCompleted使用,这里能使用onCompleted,因为使用之前已经是最后一个事件。
BehaviorSubject< Integer> s = BehaviorSubject.create();
s.onNext(0);
s.onNext(1);
s.onNext(2);
s.onCompleted();
s.subscribe(
v -> System.out.println("Late: " + v),
e -> System.out.println("Error "),
() -> System.out.println("Completed ")
);
下面代码是提供初始值:
BehaviorSubject< Integer> s = BehaviorSubject.create(0);
s.subscribe(v -> System.out.println(v));
s.onNext(1);
输出是两个数值: 0 和1
4. AsyncSubject 也会缓存最后的值,区别是,只有等整个事件系列完成时才会发送,否则一个值都不会发送。一旦完成发送一个单个值。
AsyncSubject< Integer> s = AsyncSubject.create();
s.subscribe(v -> System.out.println(v));
s.onNext(0);
s.onNext(1);
s.onNext(2);
s.onCompleted();
输出:2
如果我们这里不调用onCompleted()方法,这里就不会有任何输出。
隐式约定
RxJava有一个潜在的约定,在事件中断(onError 或 onCompleted)时不会有任何事件发送,Subject实现也是遵循这样的约定,subscribe也会阻止违背约定的情况发生。
Subject< Integer, Integer> s = ReplaySubject.create();
s.subscribe(v -> System.out.println(v));
s.onNext(0);
s.onCompleted();
s.onNext(1);
s.onNext(2);
输出是: 0