响应式编程是什么,在上一篇规约中已经提到过,这里不再赘述。那接下来我们将深入检出的掌握RxJava。
目录
1. RxJava背景
2. 官方定义
3. 实现原理
3.1 基本实现步骤
3.2 Observer 和 Subscriber的区别
4. Rx1.x
4.1 事件流程
4.2 Sync & Async
4.3 操作符
4.4 背压
4.5 Scheduler
4.6 Hot & Cold
5. Rx2.x
5.1 Disposable
5.2 五种观察者模式
5.3 Observable & Observer
5.4 Flowable & Subscriber
5.5 Single & SingleObserver
5.6 Completable & CompletabeObserver
5.7 Maybe & MaybeObserver
6. 并行
6.1 Flowable.flatMap
6.2 ParallelFlowable
ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源,Rx库支持.NET、JavaScript和C++,Rx近几年越来越流行了,现在已经支持几乎全部的流行编程语言了,Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 reactivex.io。
主要版本:RxJava 1.x(官方已宣布停止维护),RxJava 2.x(全新的API)
微软给的定义是,Rx是一个函数库,让开发者可以利用可观察序列和LINQ风格查询操作符来编写异步和基于事件的程序,使用Rx,开发者可以用Observables表示异步数据流,用LINQ操作符查询异步数据流, 用Schedulers参数化异步数据流的并发处理,Rx可以这样定义:Rx = Observables + LINQ + Schedulers。
ReactiveX.io给的定义是,Rx是一个使用可观察数据流进行异步编程的编程接口,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。
RxJava给的定义是,Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。
总之:Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,响应式编程的解决方案、观察者设计模式、一个实现异步操作的库。
rxjava是通过扩展的观察者模式来实现的,Observable是异步的双向push,Iterable是同步的单向pull。
基本实现通过3个步骤
1. 创建 Observer
Observer 即观察者,它决定事件触发的时候将有怎样的行为。除了 Observer
接口之外,RxJava 还内置了一个实现了 Observer
的抽象类:Subscriber
。 Subscriber
对 Observer
接口进行了一些扩展,但他们的基本使用方式是完全一样的:
Observer observer = new Observer() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
Subscriber subscriber = new Subscriber() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
不仅基本使用方式一样,实际上,在 RxJava 的 subscribe 过程中,Observer
也总是会先被转换成一个 Subscriber
再使用。
2. 创建 Observable
Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。create()
方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列,例如:
Observable observable = Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("Hi");
subscriber.onNext("Aloha");
subscriber.onCompleted();
}
});
/**
* just(T...): 将传入的参数依次发送出来,将会依次调用:
* onNext("Hello");
* onNext("Hi");
* onNext("Aloha");
* onCompleted();
**/
Observable observable = Observable.just("Hello", "Hi", "Aloha");
/**
* from(T[]) / from(Iterable extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送* 出来
* 将会依次调用:
* onNext("Hello");
* onNext("Hi");
* onNext("Aloha");
* onCompleted();
**/
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
3. 订阅 Subscribe
创建了 Observable
和 Observer
之后,再用 subscribe()
方法将它们联结起来,整条链子就可以工作了。
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);
subscriber()
做了3件事,另一篇文章中展开讲
Observer
和 Subscriber的区别
前面讲过,Observer最终会被转出Subscriber
再使用,故在使用上二者都可以,但也有本质的区别:
onStart(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用。可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。
被观察者(Observable)通过订阅(Subscribe)按顺序发送事件给观察者(Observer),观察者(Observer)按顺序接收事件&作出对应的响应动作,见下图
其中订阅subscribe()的工作流程见下图:
方法调用顺序:观察者.onSubscribe()> 被观察者.subscribe()> 观察者.onNext()>观察者.onComplete()
Observable通过使用最佳的方式访问异步数据序列填补了这个间隙
|
单个数据 |
多个数据 |
同步 |
|
|
异步 |
|
|
Observable和Observer仅仅是个开始,它们本身不过是标准观察者模式的一些轻量级扩展,目的是为了更好的处理事件序列。Rx的操作符让你可以用声明式的风格组合异步操作序列,它拥有回调的所有效率优势,同时又避免了典型的异步系统中嵌套回调的缺点。
背压(Backpressure)是指在异步场景中,被观察者发送事件速度远远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略。(即背压是一种控制流速的策略,前提是异步,就是被观察者跟观察者处在不同的线程环境中)。
Backpressure : 指的是在 Buffer 有上限的系统中,Buffer 溢出的现象;它的应对措施只有一个:丢弃新事件。
背压策略的实现方式(即响应式拉取:reactive pull)
在Rxjava的观察者模型中,被观察者是主动的推动数据给观察者的,而观察者是被动接收的,而响应式则反过来,观察者主动从被观察者那里去拉取数据,而被观察者则变成被动的等待通知再发送数据,即观察者可以根据自身实际情况按需拉取数据,而不是被动接收(这就可以达到告诉被观察者把速度慢下来),从而实现对被观察者发送事件速度的控制,从而实现背压。总结起来就是:背压是一种策略,具体措施是下游观察者通知上游的被观察者发送事件,背压策略很好的解决了异步环境下被观察者和观察者速度不一致的问题。
在Rxjava1.x中,关于背压都是集中在Observable这个类中,导致有的Observable支持背压,有的不支持,Rxjava2.0为了解决这种缺憾,把支持背压跟不支持背压的Observable区分开来。
Flowable背压策略 |
描述 |
BackpressureStrategy.ERROR |
缓存区默人大小128,流速不均衡时发射MissingBackpressureException信号 |
BackpressureStrategy.BUFFER |
缓存区不限制大小,使用不当仍会OOM |
BackpressureStrategy.DROP |
缓存最近的nNext事件 |
BackpressureStrategy.LATEST |
缓存区会保留最后的OnNext事件,覆盖之前缓存的OnNext事件 |
BackpressureStrategy.MISSING |
OnNext事件没有任何缓存和丢弃,下游要处理任何溢出 |
由Observer变成了Subscriber,主要区别是增加了void request(long n)方法;
由ObservableEmitter变成FlowableEmitter,主要区别是增加了 long requested();
参考实例
在 RxJava 的默认规则(在哪个线程调用 subscribe()
,就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件),事件的发出和消费都是在同一个线程的。
如果你想给Observable操作符链添加多线程功能,你可以指定操作符在特定的调度器(Scheduler)上执行。
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(@NonNull ObservableEmitter subscriber) throws Exception {
System.out.println("create - 当前线程信息:" + Thread.currentThread().getName());
subscriber.onNext(20);
subscriber.onNext(3);
subscriber.onNext(3);
subscriber.onComplete();
}
}).subscribeOn(Schedulers.computation()).observeOn(Schedulers.io())
.subscribe(new Observer() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
System.out.println("onSubscribe - 当前线程信息:" + Thread.currentThread().getName());
}
@Override
public void onNext(@NonNull Integer integer) {
System.out.println("onNext - 当前线程信息:" + Thread.currentThread().getName());
}
@Override
public void onError(@NonNull Throwable throwable) {
System.out.println("onError - 当前线程信息:" + Thread.currentThread().getName());
}
@Override
public void onComplete() {
System.out.println("onComplete - 当前线程信息:" + Thread.currentThread().getName());
}
});
/**
onSubscribe - 当前线程信息:main
create - 当前线程信息:RxComputationThreadPool-1
onNext - 当前线程信息:RxCachedThreadScheduler-1
onNext - 当前线程信息:RxCachedThreadScheduler-1
onNext - 当前线程信息:RxCachedThreadScheduler-1
onComplete - 当前线程信息:RxCachedThreadScheduler-1
**/
SubscribeOn操作符
叫事件产生的线程,它指示Observable将全部的处理过程(包括发射数据和通知)放在特定的调度器上执行,即 Observable.OnSubscribe
被激活时所处的线程。
指定 subscribeOn(Schedulers.io()) 时,被创建的事件的内容 1
、2
、3
、4
将会在 IO 线程发出。
ObserveOn操作符
叫事件消费的线程,它指示Observer在一个特定的调度器上执行观察者的onNext,onError和onCompleted方法,指定 Subscriber
所运行在的线程。
具体的原则:
执行流程
注:上图画的不准确,map前缺失observeOn
调度器的种类
调度器类型 |
效果 |
Schedulers.immediate( ) |
直接在当前线程运行,相当于不指定线程。这是默认的 |
Schedulers.newThread( ) |
总是启用新线程,并在新线程执行操作 |
Schedulers.io( ) |
用于IO密集型任务,如异步阻塞IO操作(读写文件、读写数据库、网络信息交互等),行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池(线程池会根据需要增长),可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程,对于普通的计算任务,请使用Schedulers.computation()。Schedulers.io( )默认是一个CachedThreadScheduler,很像一个有线程缓存的新线程调度器 |
Schedulers.computation( ) |
用于计算任务,使用的固定的线程池,大小为 CPU 核数。 这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算,事件循环或和回调处理。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。 |
Schedulers.from(executor) |
使用指定的Executor作为调度器 |
延时和周期调度器
someScheduler.schedule(someAction, 500, TimeUnit.MILLISECONDS);//500毫秒之后开始执行;
someScheduler.schedulePeriodically(someAction, 500, 250, TimeUnit.MILLISECONDS);//任务将在500毫秒之后执行,然后每250毫秒执行一次;
小结阻塞&非阻塞
理解冷热两种模式下的Observables对于掌握Observables来说至关重要,我们先来读一读RxJS官方的定义:
Cold Observables在被订阅后运行,也就是说,observables序列仅在subscribe函数被调用后才会推送数据。与Hot Observables不同之处在于,Hot Observables在被订阅之前就已经开始产生数据,例如mouse move事件。
let obs = Rx.Observable.create(observer => observer.next(1));
obs.subscribe(v => console.log("1st subscriber: " + v));
obs.subscribe(v => console.log("2nd subscriber: " + v));
//运行结果:
1st subscriber: 1
2nd subscriber: 1
那问题来了,代码中的obs是冷模式还是热模式?
分析上面官方的解释,如果obs
是冷模式,那么它被订阅后才会产生“新鲜”的数据。为了体现“新鲜”这一点,我们用Date.now()
来替代数字1
, 我们注意到两次获得的数据并不相同,这意味着observer.next(Date.now())
一定被调用了两次。
那该怎么破?
当然冷模式是可以转换为热模式的,使用ConnectableFlowable & publish & connect。
let obs = Rx.Observable
.create(observer => observer.next(Date.now()))
.publish();
obs.subscribe(v => console.log("1st subscriber: " + v));
obs.subscribe(v => console.log("2nd subscriber: " + v));
obs.connect();
publish |
publish 返回一个 ConnectableObservable 对象,它对于数据源共享同一个订阅,但还没有订阅到数据源(像一个守门员,保证所有的订阅都订阅到 public final Subscription connect() |
connect |
connect 操作符使ConnectableObservable 实际订阅到数据源(如果不调用connect 函数则不会触发数据流的执行)。当调用 connect 函数以后,会创建一个新的subscription 并订阅到源 Observable,这个 subscription 开始接收数据并把它接收到的数据转发给所有的订阅者。这样,所有的订阅者在同一时刻都可以收到同样的数据。 |
refCount |
refCount 返回一个特殊的 Observable,这个 Observable 只要有订阅者就会继续发射数据,实际上对 Observable |
replay |
replay 将Flowable变成 ConnectableFlowable, 在connect之后,确保每次消费都使用相同数据(会保存历史数据进行回放)。 |
如何使用?
这儿有一条经验:当你有一个冷模式的Observable而又想不同的订阅者订阅它时获得之前产生过的数据时,你可以使用publish和它的小伙伴们。
Disposable
这个东西可以直接调用切断,可以看到,当它的 isDisposed() 返回为 false 的时候,接收器能正常接收事件,但当其为 true 的时候,接收器停止了接收。所以可以通过此参数动态控制接收事件了。
Observable & Observer |
见下 |
Flowable & Subscriber |
见下 |
Single & SingleObserver |
见下 |
Completable & CompletabeObserver |
见下 |
Maybe & MaybeObserver |
见下 |
//基本用法与v1.x类似,略
Observable.create(new ObservableOnSubscribe() {
// 1. 创建被观察者 & 生产事件
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
System.out.println("subscribe传递");
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onComplete();
}
}).subscribe(new Observer() {
// 2. 通过通过订阅(subscribe)连接观察者和被观察者
// 3. 创建观察者 & 定义响应事件的行为
@Override
public void onSubscribe(Disposable d) {
System.out.println("开始采用subscribe连接");
}
// 默认最先调用复写的 onSubscribe()
@Override
public void onNext(Integer value) {
System.out.println("对Next事件" + value + "作出响应");
}
@Override
public void onError(Throwable e) {
System.out.println("对Error事件作出响应");
}
@Override
public void onComplete() {
System.out.println("对Complete事件作出响应");
}
});
//大部分用法与Observable类似,多数情况下使用背压时需要它,
Flowable.create(new FlowableOnSubscribe() {
@Override
public void subscribe(@NonNull FlowableEmitter e) throws Exception {
int i = 0;
while(i < Long.MAX_VALUE){
e.onNext(i);
i++;
}
}
}, BackpressureStrategy.DROP)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe(new Subscriber() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("Test","i = "+integer);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
只发射一条单一的数据,或者一条异常通知,不能发射完成通知,其中数据与通知只能发射一个。
Single.create(new SingleOnSubscribe() {
@Override
public void subscribe(@NonNull SingleEmitter singleEmitter) throws Exception {
singleEmitter.onSuccess("hello");
//singleEmitter.onError(new Exception("测试异常"));
}
}).subscribe(new SingleObserver() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
}
@Override
public void onSuccess(@NonNull Object o) {
System.out.println("onSuccess");
}
@Override
public void onError(@NonNull Throwable throwable) {
System.out.println("onError");
}
});
只发射一条完成通知,或者一条异常通知,不能发射数据,其中完成通知与异常通知只能发射一个
Completable.create(new CompletableOnSubscribe() {
@Override
public void subscribe(@NonNull CompletableEmitter completableEmitter) throws Exception {
//completableEmitter.onComplete();
completableEmitter.onError(new Exception("测试异常"));
}
}).subscribe(new CompletableObserver() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
}
@Override
public void onComplete() {
System.out.println("onComplete");
}
@Override
public void onError(@NonNull Throwable throwable) {
System.out.println("onError");
}
});
可发射一条单一的数据,以及发射一条完成通知,或者一条异常通知,其中完成通知和异常通知只能发射一个,发射数据只能在发射完成通知或者异常通知之前,否则发射数据无效。
Maybe.create(new MaybeOnSubscribe() {
@Override
public void subscribe(@NonNull MaybeEmitter maybeEmitter) throws Exception {
//maybeEmitter.onSuccess("suc");
maybeEmitter.onError(new Exception("测试异常"));
//maybeEmitter.onComplete();
}
}).subscribe(new MaybeObserver() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
}
@Override
public void onSuccess(@NonNull String s) {
System.out.println("onSuccess");
}
@Override
public void onError(@NonNull Throwable throwable) {
System.out.println("onError");
}
@Override
public void onComplete() {
System.out.println("onComplete");
}
});
RxJava的线程模型中被观察者(Observable、Flowable...)发射的数据流可以经历各种线程切换,但是数据流的各个元素之间不会产生并行执行的效果。我们知道并行并不是并发,不是同步,更不是异步。
Java8中有parallelStream(),如果要达到这个效果可以借着flatMap实现(每个Observable可以使用线程池来并发的执行)
Observable.range(1, 100).flatMap(new Function>() {
@Override
public ObservableSource apply(Integer integer) throws Exception {
return Observable.just(integer).subscribeOn(Schedulers.computation()).map(new Function() {
@Override
public String apply(Integer integer) throws Exception {
return integer.toString();
}
});
}
}).subscribe(new Consumer() {
@Override
public void accept(String str) throws Exception {
System.out.println(str);
}
});
flatMap操作符的原理是将这个Observable转化为多个以原Observable发射的数据作为源数据的Observable,然后再将这多个Observable发射的数据整合发射出来,需要注意的是最后的顺序可能会交错地发射出来。
int threadNum = Runtime.getRuntime().availableProcessors() + 1;
final ExecutorService executor = Executors.newFixedThreadPool(threadNum);
final Scheduler scheduler = Schedulers.from(executor);
Observable.range(1, 20).flatMap(new Function>() {
@Override
public ObservableSource apply(Integer integer) throws Exception {
return Observable.just(integer).subscribeOn(scheduler).map(new Function() {
@Override
public String apply(Integer integer) throws Exception {
return integer.toString();
}
});
}
}).doFinally(new Action() {//会在onError或者onComplete之后调用
@Override
public void run() throws Exception {
executor.shutdown();
}
}).subscribe(new Consumer() {
@Override
public void accept(String str) throws Exception {
System.out.println(str);
}
});
类似Java 8的并行流,在相应的操作符上调用Flowable的parallel()就会返回ParallelFlowable。
ParallelFlowable parallelFlowable = Flowable.range(1, 100).parallel();
parallelFlowable.runOn(Schedulers.io()).map(new Function() {
@Override
public Object apply(@NonNull Integer integer) throws Exception {
return integer.toString();
}
}).sequential().subscribe(new Consumer() {
@Override
public void accept(@NonNull String str) throws Exception {
System.out.println(str);
}
});
ParallelFlowable遵循与Flowable相同的异步原理,因此parallel()本身不引入顺序源的异步消耗,只准备并行流。但是可以通过runOn(可以指定prefetch的数量)操作符定义异步。这一点跟Flowable很大不同,Flowable是使用subscribeOn、observeOn操作符。
目前ParallelFlowable只支持如下的操作:
map,
filter,
flatMap,
concatMap,
reduce,
collect,
sort,
toSortedList,
compose,
doOnCancel, doOnError, doOnComplete, doOnNext, doOnSubscribe, doAfterTerminate, doOnRequest
那ParallelFlowable和Flowable.flatMap哪个更好呢?
其实实现并行的原理是一样的,RxJava 本质上是连续的,借助flatMap操作符进行分离和加入一个序列可能会变得复杂,并引起一定的开销。 但是如果使用ParallelFlowable的话开销会更小。
附录 Rxx中文翻译
附录Reactor指南中文版