响应式编程是什么,在上一篇规约中已经提到过,这里不再赘述。那接下来我们将深入检出的掌握RxJava。
目录
6.6 Completable & CompletabeObserver
1. RxJava背景
ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源,Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,Rx库支持.NET、JavaScript和C++,Rx近几年越来越流行了,现在已经支持几乎全部的流行编程语言了,Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 reactivex.io。
主要版本:RxJava 1.x(官方已宣布停止维护),RxJava 2.x(全新的API)
2. 官方定义
微软给的定义是,Rx是一个函数库,让开发者可以利用可观察序列和LINQ风格查询操作符来编写异步和基于事件的程序,使用Rx,开发者可以用Observables表示异步数据流,用LINQ操作符查询异步数据流, 用Schedulers参数化异步数据流的并发处理,Rx可以这样定义:Rx = Observables + LINQ + Schedulers。
ReactiveX.io给的定义是,Rx是一个使用可观察数据流进行异步编程的编程接口,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。
总之:响应式编程的解决方案、观察者设计模式、一个实现异步操作的库。
3. 观察这模式
角色 |
作用 |
类比 |
被观察者(Observable) |
产生事件 |
顾客 |
观察者(Observer) |
接收事件,并给出响应 |
厨房 |
订阅(Subscribe) |
连接被观察者&观察者 |
服务员 |
事件(Event) |
被观察者&观察者沟通的载体 |
菜式 |
如上图“按下开关,台灯灯亮”,可以引出Rxjava的一些重要概念:
- 开关(被观察者)作为事件的生产方(产生“开”和“关”这两个事件),是主动生产的,是整个开灯事件流程的起点;
- 台灯(观察者)作为事件的处理方(处理“灯亮”和“灯灭”这两个事件)是被动的,是整个开灯事件流程的终点;
- 在起点跟终点之间,即事件传递的过程中是可以被加工,过滤,转换,合并等等方式处理,这就是Rx中常说的操作符的职能
4. Push和Pull
你可以把Observable当做Iterable的推送方式的等价物,可以理解为:所有使用Iterable的地方都可以使用Rx,
- 使用Iterable,消费者从生产者那拉取数据,线程阻塞直至数据准备好;
- 使用Observable,在数据准备好时,生产者将数据推送给消费者(可同步可异步更灵活);
Observable是异步的双向push,Iterable是同步的单向pull,对比:
事件 |
Iterable(pull) |
Observable(push) |
获取数据 |
|
|
异常处理 |
throws |
|
任务完成 |
|
|
//伪代码:
// Iterable
getDataFromLocalMemory()
.skip(10)
.take(5)
.map({ s -> return s + " transformed" })
.forEach({ println "next => " + it });
// Observable
getDataFromNetwork()
.skip(10)
.take(5)
.map({ s -> return s + " transformed" })
.subscribe({ println "onNext => " + it });
Observable类型给GOF的观察者模式添加了两种缺少的语义,这样就和Iterable类型中可用的操作一致了:
- 生产者可以发信号给消费者,通知它没有更多数据可用了(对于Iterable,一个for循环正常完成表示没有数据了;对于Observable,就是调用观察者的onCompleted方法);
- 生产者可以发信号给消费者,通知它遇到了一个错误(对于Iterable,迭代过程中发生错误会抛出异常;对于Observable,就是调用观察者(Observer)的onError方法)
5. Rx1.x
5.1 事件流程
被观察者(Observable)通过订阅(Subscribe)按顺序发送事件给观察者(Observer),观察者(Observer)按顺序接收事件&作出对应的响应动作,见下图
其中订阅subscribe()的工作流程见下图:
方法调用顺序:观察者.onSubscribe()> 被观察者.subscribe()> 观察者.onNext()>观察者.onComplete()
5.2 Sync & Async
Observable通过使用最佳的方式访问异步数据序列填补了这个间隙
|
单个数据 |
多个数据 |
同步 |
|
|
异步 |
|
|
5.3 操作符
Observable和Observer仅仅是个开始,它们本身不过是标准观察者模式的一些轻量级扩展,目的是为了更好的处理事件序列。Rx的操作符让你可以用声明式的风格组合异步操作序列,它拥有回调的所有效率优势,同时又避免了典型的异步系统中嵌套回调的缺点。
- 创建操作 Create, Defer, Empty/Never/Throw, From, Interval, Just, Range, Repeat, Start, Timer
- 变换操作 Buffer, FlatMap, GroupBy, Map, Scan和Window
- 过滤操作 Debounce, Distinct, ElementAt, Filter, First, IgnoreElements, Last, Sample, Skip, SkipLast, Take, TakeLast
- 组合操作 And/Then/When, CombineLatest, Join, Merge, StartWith, Switch, Zip
- 错误处理 Catch和Retry
- 辅助操作 Delay, Do, Materialize/Dematerialize, ObserveOn, Serialize, Subscribe, SubscribeOn, TimeInterval, Timeout, Timestamp, Using
- 条件和布尔操作 All, Amb, Contains, DefaultIfEmpty, SequenceEqual, SkipUntil, SkipWhile, TakeUntil, TakeWhile
- 算术和集合操作 Average, Concat, Count, Max, Min, Reduce, Sum
- 转换操作 To
- 连接操作 Connect, Publish, RefCount, Replay
- 反压操作,用于增加特殊的流程控制策略的操作符
5.4 背压
背压(Backpressure)是指在异步场景中,被观察者发送事件速度远远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略。(即背压是一种控制流速的策略,前提是异步,就是被观察者跟观察者处在不同的线程环境中)。
背压策略的实现方式(即响应式拉取:reactive pull)
在Rxjava的观察者模型中,被观察者是主动的推动数据给观察者的,而观察者是被动接收的,而响应式则反过来,观察者主动从被观察者那里去拉取数据,而被观察者则变成被动的等待通知再发送数据,即观察者可以根据自身实际情况按需拉取数据,而不是被动接收(这就可以达到告诉被观察者把速度慢下来),从而实现对被观察者发送事件速度的控制,从而实现背压。总结起来就是:背压是一种策略,具体措施是下游观察者通知上游的被观察者发送事件,背压策略很好的解决了异步环境下被观察者和观察者速度不一致的问题。
在Rxjava1.x中,关于背压都是集中在Observable这个类中,导致有的Observable支持背压,有的不支持,Rxjava2.0为了解决这种缺憾,把支持背压跟不支持背压的Observable区分开来。
- Observable ( 被观察者 ) / Observer ( 观察者 )
- Flowable (被观察者)/ Subscriber (观察者)
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();
5.5 Scheduler
如果你想给Observable操作符链添加多线程功能,你可以指定操作符在特定的调度器(Scheduler)上执行,
ObserveOn操作符
它指示Observable在一个特定的调度器上调用观察者的onNext,onError和onCompleted方法,指定的就是订阅者接收事件的线程;
SubscribeOn操作符
它指示Observable将全部的处理过程(包括发射数据和通知)放在特定的调度器上执行,指定的就是发射事件的线程;
具体的原则:
- 事件产生:默认运行在当前线程,可以由 subscribeOn()自定义线程
- 事件加工:默认跟事件产生的线程保持一致, 可以由 observeOn()自定义线程
- 事件消费:默认运行在当前线程,可以有observeOn()自定义
- 如果只规定了事件产生的线程,那么事件消费线程(包含事件加工)将跟随事件产生线程。
- 如果只规定了事件消费的线程,那么事件产生的线程和当前线程保持一致。
执行流程,ref
注:上图画的不准确,map前缺失observeOn
调度器的种类
调度器类型 |
效果 |
Schedulers.computation( ) |
用于计算任务,如事件循环或和回调处理,不要用于IO操作(IO操作请使用Schedulers.io());默认线程数等于处理器的数量 |
Schedulers.from(executor) |
使用指定的Executor作为调度器 |
Schedulers.immediate( ) |
在当前线程立即开始执行任务 |
Schedulers.io( ) |
用于IO密集型任务,如异步阻塞IO操作,这个调度器的线程池会根据需要增长;对于普通的计算任务,请使用Schedulers.computation();Schedulers.io( )默认是一个CachedThreadScheduler,很像一个有线程缓存的新线程调度器 |
Schedulers.newThread( ) |
为每个任务创建一个新线程 |
Schedulers.trampoline( ) |
当其它排队的任务完成后,在当前线程排队开始执行 |
延时和周期调度器
someScheduler.schedule(someAction, 500, TimeUnit.MILLISECONDS);//500毫秒之后开始执行;
someScheduler.schedulePeriodically(someAction, 500, 250, TimeUnit.MILLISECONDS);//任务将在500毫秒之后执行,然后每250毫秒执行一次;
5.6 Hot & Cold
理解冷热两种模式下的Observables对于掌握Observables来说至关重要,我们先来读一读RxJS官方的定义:
Cold Observables在被订阅后运行,也就是说,observables序列仅在subscribe函数被调用后才会推送数据。与Hot Observables不同之处在于,Hot Observables在被订阅之前就已经开始产生数据,例如mouse move事件。
- Cold Observable在每个订阅者订阅的时候都独立的执行一遍数据流代码,Observable.interval 就是一个 Cold Observable。
- Hot observable 不管有没有订阅者订阅,他们创建后就开发发射数据流。 一个比较好的示例就是 鼠标事件。 不管系统有没有订阅者监听鼠标事件,鼠标事件一直在发生,当有订阅者订阅后,从订阅后的事件开始发送给这个订阅者,之前的事件这个订阅者是接受不到的;如果订阅者取消订阅了,鼠标事件依然继续发射。
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())
一定被调用了两次。
那该怎么破?
当然冷模式是可以转换为热模式的,
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 对象,它对于数据源共享同一个订阅,但还没有订阅到数据源(像一个守门员,保证所有的订阅都订阅到ConnectableObservable
上面,而不是数据源本身),它是 Observable 的拓展,多了三个函数:
public final Subscription connect()
public abstract void connect(Action1<? super Subscription> connection)
public Observable<T> refCount()
connect
connect
操作符使ConnectableObservable
实际订阅到数据源(如果不调用connect 函数则不会触发数据流的执行)。当调用 connect 函数以后,会创建一个新的subscription 并订阅到源 Observable,这个 subscription 开始接收数据并把它接收到的数据转发给所有的订阅者。这样,所有的订阅者在同一时刻都可以收到同样的数据。
refCount
refCount 返回一个特殊的 Observable,这个 Observable 只要有订阅者就会继续发射数据,实际上对ConnectableObservable
的所有订阅进行了记录。
Observable<Long> cold = Observable.interval(200, TimeUnit.MILLISECONDS).publish().refCount();
如何使用?
这儿有一条经验:当你有一个冷模式的Observable而又想不同的订阅者订阅它时获得之前产生过的数据时,你可以使用publish和它的小伙伴们。
6. Rx2.x
6.1 Disposable
这个东西可以直接调用切断,可以看到,当它的 isDisposed() 返回为 false 的时候,接收器能正常接收事件,但当其为 true 的时候,接收器停止了接收。所以可以通过此参数动态控制接收事件了。
6.2 五种观察者模式
Observable & Observer |
见下 |
Flowable & Subscriber |
见下 |
Single & SingleObserver |
见下 |
Completable & CompletabeObserver |
见下 |
Maybe & MaybeObserver |
见下 |
6.3 Observable & Observer
//基本用法与v1.x类似,略
Observable.create(new ObservableOnSubscribe<Integer>() {
// 1. 创建被观察者 & 生产事件
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
System.out.println("subscribe传递");
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onComplete();
}
}).subscribe(new Observer<Integer>() {
// 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事件作出响应");
}
});
6.4 Flowable & Subscriber
//大部分用法与Observable类似,多数情况下使用背压时需要它,
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(@NonNull FlowableEmitter<Integer> 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<Integer>() {
@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() {
}
});
6.5 Single & SingleObserver
只发射一条单一的数据,或者一条异常通知,不能发射完成通知,其中数据与通知只能发射一个。
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");
}
});
6.6 Completable & CompletabeObserver
只发射一条完成通知,或者一条异常通知,不能发射数据,其中完成通知与异常通知只能发射一个
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");
}
});
6.7 Maybe & MaybeObserver
可发射一条单一的数据,以及发射一条完成通知,或者一条异常通知,其中完成通知和异常通知只能发射一个,发射数据只能在发射完成通知或者异常通知之前,否则发射数据无效。
Maybe.create(new MaybeOnSubscribe<String>() {
@Override
public void subscribe(@NonNull MaybeEmitter<String> maybeEmitter) throws Exception {
//maybeEmitter.onSuccess("suc");
maybeEmitter.onError(new Exception("测试异常"));
//maybeEmitter.onComplete();
}
}).subscribe(new MaybeObserver<String>() {
@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");
}
});
7. 并行
7.1 Flowable.flatMap
RxJava的线程模型中被观察者(Observable、Flowable...)发射的数据流可以经历各种线程切换,但是数据流的各个元素之间不会产生并行执行的效果。我们知道并行并不是并发,不是同步,更不是异步。
Java8中有parallelStream(),如果要达到这个效果可以借着flatMap实现(每个Observable可以使用线程池来并发的执行)
Observable.range(1, 100).flatMap(new Function<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
return Observable.just(integer).subscribeOn(Schedulers.computation()).map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) throws Exception {
return integer.toString();
}
});
}
}).subscribe(new Consumer<String>() {
@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<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
return Observable.just(integer).subscribeOn(scheduler).map(new Function<Integer, String>() {
@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<String>() {
@Override
public void accept(String str) throws Exception {
System.out.println(str);
}
});
7.2 ParallelFlowable
类似Java 8的并行流,在相应的操作符上调用Flowable的parallel()就会返回ParallelFlowable。
ParallelFlowable parallelFlowable = Flowable.range(1, 100).parallel();
parallelFlowable.runOn(Schedulers.io()).map(new Function<Integer, Object>() {
@Override
public Object apply(@NonNull Integer integer) throws Exception {
return integer.toString();
}
}).sequential().subscribe(new Consumer<String>() {
@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指南中文版