关于
GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。
RxJava 是异步,但它比AsyncTask / Handler/… 在逻辑上更加简洁
API
RxJava 的异步实现,是通过一种扩展的观察者模式来实现的
观察者模式(Observer)
定义对象之间一对多的关系,当一个对象被修改,其他关联对象会并得到通知并自动更新。观察者模式(有时又被称为发布(publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。经典样例:Java的事件监听机制
RxJava 的观察者模式
RxJava 有四个基本概念:
方法 | 描述 | 解释 |
---|---|---|
Observable | 被观察者 | 产生事件(事件源) |
Observer | 观察者 | 根据事件作出相应的响应 |
subscribe | (订阅)、事件 | 实现了Observer的抽象类 |
Subject | (Observable + Observer) |
Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。
RxJava的Observable是扩展自设计模式中的观察者模式,添加了以下几个能力:
- onCompleted(),当没有新的可用数据时,通知Observable;
- onError(),当发生错误时,通知Observable,但不会直接将错误或异常直接抛出;
需要注意的是,onCompleted() 和 onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。
Subject可以像观察者(Observer)一样订阅一个事件源,并且可以像Observable(被观察者)一样输出它们收到的事件。RxJava提供了四种不同类型的subjects:
- PublishSubject,
- BehaviorSubject,输出它观察到的大部分最近的Items和随后观察的Items到所有的订阅者,初始化时需要一个初始值来做为最近的Items
- ReplaySubject,将它观察到的所有数据重复发送到所有订阅了的观察者;
- AsyncSubject, 在整个Observable完成后,将最后观察到的Items发送给每一个订阅者;
Observable
大致如下图:
Observable生命周期包含三个可能的事件 与迭代器的生命周期很类似
事件 | 迭代器(Iterable)pull | 被观察者(Observable )push |
---|---|---|
得到数据 | T next() | onNext(T) |
发现错误 | throws Exception | onError(Throwable) |
完成 | !hasNext() | onCompleted() |
区别:
在使用迭代器的时候,线程会阻塞直到他们需要的数据到来。
而Observable,是使用异步的方式将数据推送到Observer。
根据推送机制的不同,Observable分为热Observable和冷Observable:
- 热Observable,当他创建时新开始执行它的职责,这样所有订阅了这个Observable的Observer就可以直接大中途观察了(但可能会丢失前面发送的数据(事件));
- 冷Observable,只有等到有订阅(subscribes)了这个Observable的Observer才开始执行它的职责:发送数据;
RxJava 的基本实现主要有三点:
- 1) 创建 Observer
- 2) 创建 Observable
- 3) Subscribe (订阅)
1.1Observer
1.1.1接口实现
RxJava 中的 Observer 接口的实现方式如下
Observer<String> observer = new Observer<String>() {
@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!");
}
};
1.1.2抽象类实现
RxJava 内置了一个实现了 Observer 的抽象类:Subscriber,他们的基本使用方式是完全一样的。
Subscriber<String> subscriber = new Subscriber<String>() {
@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 再使用。所以如果只想使用基本功能,选择 Observer 和 Subscriber 是完全一样的。
Subscriber 的扩展
扩展方法 | 描述 |
---|---|
onStart() | 它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法 |
unsubscribe() | 用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。 |
doOnSubscribe():
Subscriber.onStart() 相对应的,有一个方法 Observable.doOnSubscribe() 。它和 Subscriber.onStart() 同样是在 subscribe() 调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下, doOnSubscribe() 执行在 subscribe() 发生的线程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的话,它将执行在离它最近的 subscribeOn() 所指定的线程。
示例:
Observable.create(onSubscribe)
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Action0() {
@Override
public void call() {
progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
}
})
.subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
1.2Observable
构造方法
构造方法 | 描述 |
---|---|
create(subscribe) | 需要一个subscribe作为参数来构造 |
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("Hi");
subscriber.onNext("RxJava");
subscriber.onCompleted();
}
});
构造方法 | 描述 |
---|---|
from(list) | 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来,和前面的create作用类似 |
String[] words = {"Hello", "Hi", "RxJava"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("RxJava");
// onCompleted();
构造方法 | 描述 |
---|---|
just(funnction) | 用来接收从一个方法的返回值(最多可以有9个参数),如果返回的是List,它不会去逐个遍历List的Items,而是直接输出整个List,和前面的create作用类似 |
Observable observable = Observable.just("Hello", "Hi", "RxJava");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("RxJava");
// onCompleted();
构造方法 | 描述 |
---|---|
empty() | 不输出数据,但可以正常结束 |
never() | 不输出数据,并且不会终止 |
throw() | 不输出数据,但在发生错误时终止 |
interval() | 创建一个按固定间隔发送整数序列的Observable |
timer() | 创建一个Observable在给定的延迟后发送一个特殊的值 |
1.3Subscribe
创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);
其内部实现是这样的(仅核心代码)
public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}
大致图片如下:
在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler 。
线程的调度(Scheduler)
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。RxJava提供了5种类型的调度者
调度器 | 描述 |
---|---|
Schedulers.io() | 使用线程池来为IO操作进行调度,内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。 |
Schedulers.computation() | 与IO无关的计算型调度,这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。 |
Schedulers.immediate() | 在当前线程中快速开始某项操作,相当于不指定线程。是方法:timeout(),timeInterval等的默认调度器 |
Schedulers.newThread() | 总是启用新线程,并在新线程执行操作 |
Schedulers.trampoline() | 为一些不需要立即执行的任务进行调度,会依次执行队列里的任务,是方法:repeat(),retry()的默认调度器 |
AndroidSchedulers.mainThread() | Android 还有一个专用的,它指定的操作将在 Android 主线程运行 |
有了 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。
- subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
- observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
调度代码如下:
Observable.just(1, 2, 3, 4)
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);
}
});
RxJava的操作符
RxJava 提供了对事件序列进行变换的支持:将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列
过滤操作
过滤方法 | 描述 |
---|---|
filter() | 过滤掉不需要的数据,只有返回true 的数据才会被使用; |
throttleFirst() | 常用作去抖动过滤,例如按钮的点击监听器 |
take(int n) | 只取返回数据中的前n个,skip(int n)跳过前n个数据; |
takeLast(int n) | 只取数据的最后n个,skipLast(int n) |
distinct() | 会帮助我们处理重复的数据,但如果数据太大的话,内存需要比较大 |
distinctUntilChanged() | 只有当新数据与先前的不同,才会输出 |
first(),last() | |
firstOrDefault() | lastOrDefault,如果Observable没有输出任何数据时,我们可以给一个默认值 |
elementAt(int n) | 输出第n个位置上的数据(从0 开始) |
timeout() | 如果在给定时间间隔内,没有输出有效数据,则会执行onError() |
delay() | 用于事件流中,延迟一段时间再发送来自Observable的结果 |
映射变换操作
针对事件序列的处理和再发送
映射变换方法 | 描述 |
---|---|
map() | 事件对象的直接变换用来映射简单的数据 |
flatMap() | 把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。 由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求 |
lift() | 像一种代理机制,通过事件拦截和处理实现事件序列的变换。在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。 两次和多次的 lift() 同理 |
compose() | 对 Observable 整体的变换 observable1.compose(liftAll).subscribe(subscriber1); |
concatMap() | 解决了fmp的交错的问题 |
flatMapIterable() | 将生成的Iterable与Items进行对应起来(类似于key-value) |
switchMap() | 这几个方法都是将输入的数据以一种新的形式输出 |
Scan() | 类似于一个累加的方法,后一个item是前面item的后再加上原来的item |
GroupBy() | |
buffer(int n) | 将数据作为列表(每n个数据作为一个列表)输出而不是单个的Items |
cast() | 类似于map() |
合并操作
合并方法 | 描述 |
---|---|
merge() | 可以将多个输入整合成一个输出(并不会合并Items) |
zip() | 可以将多个输入整合成一个输出(会合并Items) |
重试操作
重试方法 | 描述 |
---|---|
retryWhen() | 当接收到onError()事件时,触发重新订阅(发生某些错误时,需要做什么工作) |
repeat() | 当接收到onComplete()事件时,触发重新订阅 |
数据变换中线程的自由切换
Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map(mapOperator) // 新线程,由 observeOn() 指定
.observeOn(Schedulers.io())
.map(mapOperator2) // IO 线程,由 observeOn() 指定
.observeOn(AndroidSchedulers.mainThread)
.subscribe(subscriber); // Android 主线程,由 observeOn() 指定