本文基于扔物线的 给 Android 开发者的 RxJava 详解 提炼简化,感谢。
RxJava 是什么
一个词:异步。
说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。
RxJava 好在哪里
一个词:简洁。
能把什么复杂逻辑都能穿成一条线的简洁。
API 介绍和原理解析
1.概念:扩展的观察者模式
四个基本概念:Observable、Observer、subscribe、事件。
和传统的观察者模式不同,RxJava 的事件回调除了普通事件 onNext() (相当于 onClick()/onEvent()) 之外,还定义了两个特殊的事件 onCompleted() 和 onError()。
onCompleted():事件队列完结。当不再有 onNext() 发出时,需要触发 onCompleted() 作为结束。
onError():事件队列异常。在事件处理过程中出异常时,onError 会被触发,同时队列自动终止,不再有事件发出。
在一个正确运行的事件序列中,onCompleted 和 onError 有且只有一个,并且是事件序列中的最后一个。它们是互斥的。
2.基本实现
1)创建 Observer
Observer 即观察者,它决定事件触发时将有怎样的行为。
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s){
}
@Override
public void onCompleted(){
}
@Override
public void onError(Throwable e){
}
}
除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。Subscriber 对 Observer 接口进行了一些扩展,但它们的基本使用方式完全一样。
Subscriber<String> observer = new Subscriber<String>() {
@Override
public void onNext(String s){
}
@Override
public void onCompleted(){
}
@Override
public void onError(Throwable e){
}
}
在 RxJava 的 subscribe 过程中,observer 会先被转换成一个 Subscriber 再使用。它们的区别主要有两点:
- onStart
这是 Subscriber 新增的方法。他会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认实现为空。如果对准备工作的线程有要求(如弹 dialog,必须在主线程),onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubScribe() 方法,后面会提到。
- unsubscribe
这是 Subscriber 实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接受事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。unsubscribe() 这个方法很重要,因为在 subscribe() 之后,observable 会持有 Subscriber 的引用,如果不及时释放,可能会内存泄漏。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(onPause、onStop 等)调用 unsubscribe() 来解除引用关系,避免内存泄漏。
2)创建 Observable
Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("Hello");
subscriber.onNext("Hello");
subscriber.onCompleted();
}
});
Observable.OnSubscribe 即被观察的对象,上面是一个 String 对象。
call 方法里是订阅者订阅后的操作,在 subscribe 后会进行调用。
除了 create,RxJava 还提供了 just、from 等方法来简化操作:
just(T...)
: 将传入的参数依次发送出来。
Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
from(T[]) / from(Iterable<? extends T>)
: 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
3)Subscribe
创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);
Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):
// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}
可以看到,subscriber() 做了3件事:
- 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
- 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
- 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().
4)场景示例
由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中。
Subscriber<Drawable> subscriber = new Subscriber<Drawable>() {
@Override
public void onNext(Drawable drawable){
imageView.setImageDrawablw(drawable);
}
...
};
Observable observable = Observerable.create(new Observable.OnSubscribe<Drawable>(){
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
});
observable.subscribe(subscriber);
3.线程控制——Scheduler(一)
在不指定线程的情况下,RxJava 线程不变,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在那个生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler。
1)Scheduler 的 API(一)
在 RxJava 中,Scheduler 相当于线程控制器,可以指定哪一段代码运行在哪个线程。RxJava 内置了几个 Scheduler,已经适合大多数场景:
- Schedulers.immediate():直接在当前线程运行,相当于不指定。这是默认的 Scheduler。
- Schedulers.newThread():总是启用新线程,并在新线程执行操作。
- Schedulers.io():I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 内部实现了一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
- Schedulers.computation():计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
- Andoid 中有一个专用的 AndroidSchedulers.mainThread(),它在主线程运行。
Scheduler 的调用:
- subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
- observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
如上面的取 Drawable 的例子:
Subscriber<Drawable> subscriber = new Subscriber<Drawable>() {
@Override
public void onNext(Drawable drawable){
imageView.setImageDrawablw(drawable);
}
...
};
Observable observable = Observerable.create(new Observable.OnSubscribe<Drawable>(){
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
});
observable.subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread())// // 指定 Subscriber 的回调发生在主线程
.subscribe(subscriber);
这样,加载图片会发生在 IO 线程,设置图片在主线程。即使加载图片耗费了几十展示几百毫秒的世界,也不会造成界面的卡顿。
4.变换
RxJava 提供了对事件序列进行变换的支持。所谓“变换”,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。
1)API
Obaservable.just("images/logo.png")
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) {
return getBitmapFromPath(filePath);
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
showBitmap(bitmap);
}
});
Func1 类是 RxJava 的一个接口,用于包装含有一个参数的方法。FuncX 和 ActionX 的区别在于,FuncX 包装的是有返回值的方法。
map() 方法将参数中的 String 对象转换从一个 Bitmap 对象后返回,而在经过 map 之后,事件的参数类型也由 String 转为了 Bitmap。这是最常见也最容易理解的变换,但 RxJava 的变换不止如此,它不仅可以针对事件对象,也可以针对整个事件序列。
flatMap
这是一个很有用但很难理解的变换。
如要打印一串 Student 的名字
Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onNext(String name){
Log.d(tag, name);
}
...
};
Observable.from(students)
.map(new Func1<Student, String>() {
@Override
public String call(Student student){
return student.getName();
}
})
.subsrcibe(subscriber);
再假设:要打印出每个学生要修的所有课程的名字呢?
Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
@Override
public void onNext(Student student){
List<Course> courses = student.getCourses();
for (int i = 0; i < courses.size(); i++) {
Course course = courses.get(i);
Log.d(tag, course.getName());
}
}
...
};
Observable.from(students)
.subsrcibe(subscriber);
如果不想在 onNext 里使用 for 循环,而是直接处理 Course 对象呢?
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
@Override
public void onNext(Course course){
Log.d(tag, course.getName);
}
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>(){
@Override
public Observable<Course> call(Student student){
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
map 和 flatMap 的区别
Observable.from(students)
.map(new Func1<Student, Observable<Course>>(){
@Override
public Observable<Course> call(Student student){
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>(){
@Override
public Observable<Course> call(Student student){
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
map
通过 Student–>Observable\ 的 map,将 Student 序列变成 Observable\ 序列。
flatMap
通过 Student–>Observable\ 的 map,将 Student 序列变成 Observable\ 序列。
再通过 flat(铺平),将 Observable\ 序列变成 course 序列。
所有其实应该叫 mapFlat。
2) 变换的原理 lift()
变换的功能虽然各有不同,但实质上都是针对事件序列的处理和再发送。在 RxJava 内部,他们基于同一个基础的变换方法:lift(Operator)。首先看一下 lift() 的内部实现(仅核心代码):
// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
return Observable.create(new OnSubscribe<R>() {
@Override
public void call(Subscriber subscriber) {
Subscriber newSubscriber = operator.call(subscriber);
newSubscriber.onStart();
onSubscribe.call(newSubscriber);
}
});
}
在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。
变换的具体效果通过 Operator 实现,Operator 的 call 方法接受一个 Subscriber 参数,返回一个新的 Subscriber,这个新的 Subscriber 订阅了原始的 Observable,并在原始的 Observable 变化时调用旧的 Subscriber 的处理方法。
总结:
要让 Subscriber\ 能够订阅 Observable\,就要通过 Observable\ 产生一个 Observable\,同时 Observable\ 能够订阅 Observable\,Observable\ 里就需要一个 Subscriber\。
Operator 做的就是将 Subscriber\ 包装为 Subscriber\,通过 Subscriber\ 可以调用 Subscriber\。
即使不用变换,Subscriber\ 直接订阅 Observable\,然后再做 A-B 的变换也是可以的,就像上面取课程名的 for 循环,变换的好处就在于可以将一系列操作转为链式,逻辑更加清晰。
3) compose:对 Observable 整体的变换
除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。它和 lift() 的区别在于, lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable 自身进行变换。举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:
public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
@Override
public Observable<String> call(Observable<Integer> observable) {
return observable
.lift1()
.lift2()
.lift3()
.lift4();
}
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);
使用 compose() 方法,Observable 可以利用传入的 Transformer 对象的 call 方法直接对自身进行处理,不必被包在方法的里面了。
5.线程控制——Scheduler(二)
1)Scheduler 的 API(二)
可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。
observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。
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() 指定
如上,通过 observeOn() 的多次调用,程序实现了线程的多次切换。
不过,不同于 observeOn() , subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。
2)Scheduler 的原理(二)
其实, subscribeOn() 和 observeOn() 的内部实现,也是用的 lift()。
subscribeOn() 和 observeOn() 都做了线程切换的工作。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。
3) 延伸:doOnSubscribe()
然而,虽然超过一个的 subscribeOn() 对事件处理的流程没有影响,但在流程之前却是可以利用的。
在前面讲 Subscriber 的时候,提到过 Subscriber 的 onStart() 可以用作流程开始前的初始化。然而 onStart() 由于在 subscribe() 发生时就被调用了,因此不能指定线程,而是只能执行在 subscribe() 被调用时的线程。这就导致如果 onStart() 中含有对线程有要求的代码(例如在界面上显示一个 ProgressBar,这必须在主线程执行),将会有线程非法的风险,因为有时你无法预测 subscribe() 将会在什么线程执行。
而与 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);
如上,在 doOnSubscribe()的后面跟一个 subscribeOn() ,就能指定准备工作的线程了。