RxJava 原理深挖 (一)

989 阅读17分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

由于是本人闲暇时间写出来的一些见解,篇幅较长,所以分为几个部分发布出来,可以看我主页拿PDF版本的

前言

RxJava 到底是什么?

鉴于 RxJava 目前这种既火爆又神秘的现状,而我又在一年的使用过程中对 RxJava 有了一些理解,

我决定写下这篇文章来对 RxJava 做一个相对详细的、针对 Android 开发者的介绍。

我的目的有两个:

  1. 给对 RxJava 感兴趣的人一些入门的指引

  2. 给正在使用 RxJava 但仍然心存疑惑的人一些更深入的解析

在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码:

Github:github.com/ReactiveX/R… github.com/ReactiveX/R…

引入依赖:

compile 'io.reactivex:rxjava:1.0.14'`
compile 'io.reactivex:rxandroid:1.0.1'`

RxJava 到底是什么

一个词:异步。

RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based

programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组

成异步的、基于事件的程序的库 )。这就是 RxJava ,概括得非常精准。

然而,对于初学者来说,这太难看懂了。因为它是一个『总结』,而初学者更需要一个『引言』。

其实, RxJava 的本质可以压缩为异步这一个词。说到根上,它就是一个实现异步操作的库,而别的定语

都是基于这之上的。

RxJava 好在哪

换句话说,『同样是做异步,为什么人们用它,而不用现成的 AsyncTask / Handler / XXX / ...?』

一个词:简洁。

异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写

也难被读懂。 Android 创造的 AsyncTask 和 Handler ,其实都是为了让异步代码更加简洁。RxJava

的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。

假设有这样一个需求:界面上有一个自定义的视图 imageCollectorView ,它的作用是显示多张

图片,并能使用 addImage(Bitmap) 方法来任意增加显示的图片。现在需要程序将一个给出的目录数

组 File[] folders 中每个目录下的 png 图片都加载出来并显示在 imageCollectorView 中。需要注

意的是,由于读取图片的这一过程较为耗时,需要放在后台执行,而图片的显示则必须在 UI 线程执行。

常用的实现方式有多种,我这里贴出其中一种:

new Thread() {

@Override

public void run() {

super.run();

for (File folder : folders) {

File[] files = folder.listFiles();

for (File file : files) {

if (file.getName().endsWith(".png")) {

final Bitmap bitmap = getBitmapFromFile(file);

getActivity().runOnUiThread(new Runnable() {

@Override

public void run() {

imageCollectorView.addImage(bitmap);
                      }
                    });
                  }
                }
              }
            }
  }.start();

而如果使用 RxJava ,实现方式是这样的:

Observable.from(folders)

.flatMap(new Func1<File, Observable<File>>() {

@Override

public Observable<File> call(File file) {

return Observable.from(file.listFiles());

}

})

.filter(new Func1<File, Boolean>() {

@Override

public Boolean call(File file) {

return file.getName().endsWith(".png");

}

})

.map(new Func1<File, Bitmap>() {

@Override

public Bitmap call(File file) {

return getBitmapFromFile(file);

}})

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Action1<Bitmap>() {

@Override

public void call(Bitmap bitmap) {

imageCollectorView.addImage(bitmap);

}

});

那位说话了:『你这代码明明变多了啊!简洁个毛啊!』大兄弟你消消气,我说的是逻辑的简洁,

不是单纯的代码量少(逻辑简洁才是提升读写代码速度的必杀技对不?)。

观察一下你会发现, RxJava 的这个实现,是一条从上到下的链式调用,没有任何嵌套,这在逻辑

的简洁性上是具有优势的。

当需求变得复杂时,这种优势将更加明显(试想如果还要求只选取前 10 张图片,常规方式要怎么

办?如果有更多这样那样的要求呢?再试想,在这一大堆需求实现完两个月之后需要改功能,当你翻回

这里看到自己当初写下的那一片迷之缩进,你能保证自己将迅速看懂,而不是对着代码重新捋一遍思

路?)。

另外,如果你的 IDE 是 Android Studio ,其实每次打开某个 Java 文件的时候,你会看到被自动

Lambda 化的预览,这将让你更加清晰地看到程序逻辑:

Observable.from(folders)

.flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })

.filter((Func1) (file) -> { file.getName().endsWith(".png") })

.map((Func1) (file) -> { getBitmapFromFile(file) })

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });

如果你习惯使用 Retrolambda ,你也可以直接把代码写成上面这种简洁的形式。而如果你看到这

里还不知道什么是 Retrolambda ,我不建议你现在就去学习它。

原因有两点:

  1. Lambda 是把双刃剑,它让你的代码简洁的同时,降低了代码的可读性,因此同时学习 RxJava 和 Retrolambda可能会让你忽略RxJava 的一些技术细节;

  2. Retrolambda 是 Java 6/7 对 Lambda 表达式的非官方兼容方案,它的向后兼容性和稳定性是无法保障的,因此对于企业项目,使用 Retrolambda 是有风险的。所以,与很多 RxJava 的推广者不同,我并不推荐在学习 RxJava 的同时一起学习Retrolambda。

事实上,我个人虽然很欣赏 Retrolambda,但我从来不用它。

在Flipboard 的 Android 代码中,有一段逻辑非常复杂,包含了多次内存操作、本地文件操作和网络操

作,对象分分合合,线程间相互配合相互等待,一会儿排成人字,一会儿排成一字。如果使用常规的方

法来实现,肯定是要写得欲仙欲死,然而在使用 RxJava 的情况下,依然只是一条链式调用就完成了。它

很长,但很清晰。

所以, RxJava 好在哪?就好在简洁,好在那把什么复杂逻辑都能穿成一条线的简洁。

如果你来设计Rxjava 你会考虑哪些问题

例如

  1. 如何给每一个任务设置观察者模式,以及如何在讲中间可能出现的任意情况通知给最终的观

察者,最终的观察者只需要得到结果,哪怕是失败的 还是成功的

  1. 责任链模式 究竟该不该用建造者模式实现

  2. 如何通过责任链模式还能无限增加 被观察者 和观察者API 介绍和原理简析

这个我就做不到一个词说明了……因为这一节的主要内容就是一步步地说明 RxJava 到底怎样做到了异

步,怎样做到了简洁。

API 介绍和原理简析

1. 概念:扩展的观察者模式

RxJava 的异步实现,是通过一种扩展的观察者模式来实现的。

观察者模式

先简述一下观察者模式,已经熟悉的可以跳过这一段。

观察者模式面向的需求是:A 对象(观察者)对 B 对象(被观察者)的某种变化高度敏感,需要在 B 变

化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施

抓捕。在这个例子里,警察是观察者,小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证

不会漏过任何瞬间。程序的观察者模式和这种真正的『观察』略有不同,观察者不需要时刻盯着被观察

者(例如 A 不需要每过 2ms 就检查一次 B 的状态),而是采用注册(Register)或者称为订阅

(Subscribe)的方式,告诉被观察者:我需要你的某某状态,你要在它变化的时候通知我。 Android 开

发中一个比较典型的例子是点击监听器 OnClickListener 。对设置 OnClickListener 来说, View

是被观察者, OnClickListener 是观察者,二者通过 setOnClickListener() 方法达成订阅关系。

订阅之后用户点击按钮的瞬间,Android Framework 就会将点击事件发送给已经注册的

OnClickListener 。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高

的反馈速度。当然,这也得益于我们可以随意定制自己程序中的观察者和被观察者,而警察叔叔明显无

法要求小偷『你在作案的时候务必通知我』。

OnClickListener 的模式大致如下图:

如图所示,通过 setOnClickListener() 方法, Button 持有 OnClickListener 的引用(这一过程

没有在图上画出);当用户点击时, Button 自动调用 OnClickListener 的 onClick() 方法。另

外,如果把这张图中的概念抽象出来( Button -> 被观察者、 OnClickListener -> 观察者、

setOnClickListener() -> 订阅, onClick() -> 事件),就由专用的观察者模式(例如只用于监听控

件点击)转变成了通用的观察者模式。如下图:

而 RxJava 作为一个工具库,使用的就是通用形式的观察者模式。

RxJava 的观察者模式

RxJava 有四个基本概念: Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe

(订阅)、事件。 Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable

可以在需要的时候发出事件来通知 Observer 。

与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() /

onEvent() )之外,还定义了两个特殊的事件: onCompleted() 和 onError() 。

onCompleted() : 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。

RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。

onError() : 事件队列异常。在事件处理过程中出异常时, onError() 会被触发,同时队列自动

终止,不允许再有事件发出。

在一个正确运行的事件序列中, onCompleted() 和 onError() 有且只有一个,并且是事件序列中

的最后一个。需要注意的是, onCompleted() 和 onError() 二者也是互斥的,即在队列中调用

了其中一个,就不应该再调用另一个。

RxJava 的观察者模式大致如下图:

2. 基本实现

基于以上的概念, RxJava 的基本实现主要有三点:

1) 创建 Observer

Observer 即观察者,它决定事件触发的时候将有怎样的行为。 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!");

}

};

除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类: Subscriber 。

Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的:

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 是完全一样

的。它们的区别对于使用者来说主要有两点:

  1. onStart() : 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调

用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现

为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在

主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能

指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的

文中看到。

  1. 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("Hi");

subscriber.onNext("Aloha");

subscriber.onCompleted();

}

});

可以看到,这里传入了一个 OnSubscribe 对象作为参数。 OnSubscribe 会被存储在返回的

Observable 对象中,它的作用相当于一个计划表,当 Observable 被订阅的时候, OnSubscribe 的

call() 方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者

Subscriber 将会被调用三次 onNext() 和一次 onCompleted() )。这样,由被观察者调用了观察者

的回调方法,就实现了由被观察者向观察者的事件传递,即观察者模式

这个例子很简单:事件的内容是字符串,而不是一些复杂的对象;事件的内容是已经定好了的,而

不像有的观察者模式一样是待确定的(例如网络请求的结果在请求返回之前是未知的);所有事件

在一瞬间被全部发送出去,而不是夹杂一些确定或不确定的时间间隔或者经过某种触发器来触发

的。总之,这个例子看起来毫无实用价值。但这是为了便于说明,实质上只要你想,各种各样的事

件发送规则你都可以自己来写。至于具体怎么做,后面都会讲到,但现在不行。只有把基础原理先

说明白了,上层的运用才能更容易说清楚。

create() 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用

来快捷创建事件队列,例如:

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();

上面 just(T...) 的例子和 from(T[]) 的例子,都和之前的 create(OnSubscribe) 的例子是等价的。

3) Subscribe (订阅)

创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以

工作了。代码形式很简单:

observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

有人可能会注意到, subscribe() 这个方法有点怪:它看起来是『 observalbe 订阅了

observer / subscriber 』而不是『 observer / subscriber 订阅了 observalbe 』,这看起

来就像『杂志订阅了读者』一样颠倒了对象关系。这让人读起来有点别扭,不过如果把 API 设计

成 observer.subscribe(observable) / subscriber.subscribe(observable) ,虽然更加符

合思维逻辑,但对流式 API 的设计就造成影响了,比较起来明显是得不偿失的。

Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):

Observable observable = Observable.just("Hello", "Hi", "Aloha");

// 将会依次调用:

// onNext("Hello");

// onNext("Hi");

// onNext("Aloha");

// onCompleted();

String[] words = {"Hello", "Hi", "Aloha"};

Observable observable = Observable.from(words);

// 将会依次调用:

// onNext("Hello");

// onNext("Hi");

// onNext("Aloha");

// onCompleted();

observable.subscribe(observer);

// 或者:

observable.subscribe(subscriber);// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。


// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。

public Subscription subscribe(Subscriber subscriber) {

subscriber.onStart();

onSubscribe.call(subscriber);

return subscriber;

}

可以看到, subscriber() 做了3件事:

  1. 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。

  2. 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运

行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而

是在它被订阅的时候,即当 subscribe() 方法执行的时候。

  1. 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe() .

整个过程中对象间的关系如下图:

除了 subscribe(Observer) 和 subscribe(Subscriber) , subscribe() 还支持不完整定义的回调,RxJava 会自动根据定义创建出 Subscriber 。形式如下:

Action1<String> onNextAction = new Action1<String>() {

// onNext()

@Override

public void call(String s) {

Log.d(tag, s);

}

};

Action1<Throwable> onErrorAction = new Action1<Throwable>() {

// onError()

@Override

public void call(Throwable throwable) {

// Error handling

}

};

Action0 onCompletedAction = new Action0() {

// onCompleted()

@Override

public void call() {

Log.d(tag, "completed");

}

};

// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()

observable.subscribe(onNextAction);

// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和

onError()

observable.subscribe(onNextAction, onErrorAction);

// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来

定义 onNext()、 onError() 和 onCompleted()

observable.subscribe(onNextAction, onErrorAction, onCompletedAction);

简单解释一下这段代码中出现的 Action1 和 Action0 。 Action0 是 RxJava 的一个接口,它只有一

个方法 call() ,这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此

Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数传入

subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了

subscribe() ,相当于其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法

call(T param) ,这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和

onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和

onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和

Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2 ,

Action3 ) 的,它们可以被用以包装不同的无返回值的方法

注:正如前面所提到的, Observer 和 Subscriber 具有相同的角色,而且 Observer 在

subscribe() 过程中最终会被转换成 Subscriber 对象,因此,从这里开始,后面的描述我将

用 Subscriber 来代替 Observer ,这样更加严谨。

4) 场景示例

下面举两个例子:

为了把原理用更清晰的方式表述出来,本文中挑选的都是功能尽可能简单的例子,以至于有些示例

代码看起来会有『画蛇添足』『明明不用 RxJava 可以更简便地解决问题』的感觉。当你看到这种

情况,不要觉得是因为 RxJava 太啰嗦,而是因为在过早的时候举出真实场景的例子并不利于原理

的解析,因此我刻意挑选了简单的情景。

a. 打印字符串数组

将字符串数组 names 中的所有字符串依次打印出来:

String[] names = ...;

Observable.from(names)

.subscribe(new Action1<String>() {

@Override

public void call(String name) {

Log.d(tag, name);

}

});

b. 由 id 取得图片并显示

由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的

时候打印 Toast 报错:


int drawableRes = ...;

ImageView imageView = ...;

Observable.create(new OnSubscribe<Drawable>() {

@Override

public void call(Subscriber<? super Drawable> subscriber) {

Drawable drawable = getTheme().getDrawable(drawableRes));

subscriber.onNext(drawable);

subscriber.onCompleted();

}

}).subscribe(new Observer<Drawable>() {

@Override

public void onNext(Drawable drawable) {

imageView.setImageDrawable(drawable);

}

@Override

public void onCompleted() {

}

@Override

public void onError(Throwable e) {

Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();

}

});

正如上面两个例子这样,创建出 Observable 和 Subscriber ,再用 subscribe() 将它们串起来,

一次 RxJava 的基本使用就完成了。非常简单。

然而,

在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实

现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机

制,因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念:

Scheduler 。