前言:

第一次接触RxJava是在前不久,一个新Android项目的启动,在评估时选择了RxJava。RxJava是一个基于事件订阅的异步执行的一个类库。听起来有点复杂,其实是要你使用过一次,就会大概明白它是怎么回事了!为是什么一个Android项目启动会联系到RxJava呢?因为在RxJava使用起来得到广泛的认可,又是基于Java语言的。自然会有善于组织和总结的开发者联想到Android!没错,RxAndroid就这样在RxJava的基础上,针对Android开发的一个库。今天我们主要是来讲解一下RxJava,在接下来的几篇博客中我会陆续带大家来认识RxAndroid,Retrofit框架的使用,这些都是目前比较火的一些技术框架!

这里是Github上RxJava的项目地址:https://github.com/ReactiveX/RxJava

技术文档Api:http://reactivex.io/RxJava/javadoc/

官方的介绍

1.支持Java6+

2.android 2.3+

3.异步的

4.基于观察者设计模式(Observer、Observable)不懂设计模式的可以移步到此:浅谈Java设计模式(十五)观察者模式(Observer)

5.Subscribe (订阅)

正式使用RxJava

用框架或者库都是为了简洁、方便,RxJava也不例外它能使你的代码逻辑更加的简洁。举个例子之前我们先来引入依赖的 gradle 代码:

  1. compile 'io.reactivex:rxjava:1.0.14'
  2. compile 'io.reactivex:rxandroid:1.0.1'

既然是基于异步,当然要在处理比较耗时的操作上才能彰显它的优势!现在我们假设有这样一个需求:
需要实现一个多个下载的图片并且显示的功能,它的作用可以添加多个下载操作,由于下载这一过程较为耗时,需要放在后台执行,而图片的显示则必须在 UI 线程执行。常用的实现方式有多种,我这里贴出其中一种:

  1. new Thread() {
  2. @Override
  3. public void run() {
  4. super.run();
  5. for (File folder : folders) {
  6. File[] files = folder.listFiles();
  7. for (File file : files) {
  8. if (file.getName().endsWith(".png")) {
  9. final Bitmap bitmap = getBitmapFromFile(file);
  10. getActivity().runOnUiThread(new Runnable() {
  11. @Override
  12. public void run() {
  13. imageCollectorView.addImage(bitmap);
  14. }
  15. });
  16. }
  17. }
  18. }
  19. }
  20. }.start();

里面的判断是不是看起来有点晕晕,当然这是我自己写的,我一眼就能看清楚里面的逻辑,但是如果换做是别人来阅读你的代码,这就比较的尴尬了!
我们来看看使用RxJava的代码:

  1. Observable.from(folders)
  2. .flatMap(new Func1<File, Observable<File>>() {
  3. @Override
  4. public Observable<File> call(File file) {
  5. return Observable.from(file.listFiles());
  6. }
  7. })
  8. .filter(new Func1<File, Boolean>() {
  9. @Override
  10. public Boolean call(File file) {
  11. return file.getName().endsWith(".png");
  12. }
  13. })
  14. .map(new Func1<File, Bitmap>() {
  15. @Override
  16. public Bitmap call(File file) {
  17. return getBitmapFromFile(file);
  18. }
  19. })
  20. .subscribeOn(Schedulers.io())
  21. .observeOn(AndroidSchedulers.mainThread())
  22. .subscribe(new Action1<Bitmap>() {
  23. @Override
  24. public void call(Bitmap bitmap) {
  25. imageCollectorView.addImage(bitmap);
  26. }
  27. });

是不是明了,虽然说算不上简单,但是习惯了就一如既往了!

如果你使用的AndroidStudio的话,你打开Java文件的时候,你会看到被自动
Lambda 化的预览,这将让你更加清晰地看到程序逻辑:

  1. Observable.from(folders)
  2. .flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
  3. .filter((Func1) (file) -> { file.getName().endsWith(".png") })
  4. .map((Func1) (file) -> { getBitmapFromFile(file) })
  5. .subscribeOn(Schedulers.io())
  6. .observeOn(AndroidSchedulers.mainThread())
  7. .subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });

不过如果你对Java8还不是很了解的话呢这一段可以暂时忽略,但是你可以移步到这里了解一下Java8:Java8部分新特性介绍

看完代码,是不是有种相见恨晚的冲动?别急,我们来慢慢了解RxJava!

前面已经提到他是基于Java观察者设计模式的,这个模式上面有给大家链接,可以去看看,这里不不坐过多的介绍,我们来介绍一下RxJava中的观察者模式:
RxJava 的观察者模式

一、说明
1)RxJava
有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe
(订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable
可以在需要的时候发出事件来通知 Observer。
2)与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() / onEvent())之外,还定义了两个特殊的事件:onCompleted() 和 onError()。
3)onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
4)onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
5)在一个正确运行的事件序列中,
onCompleted() 和 onError() 有且只有一个,并且是事件序列中的最后一个。需要注意的是,onCompleted() 和
onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

二、实现
1) 创建 Observer
Observer 即观察者,它决定事件触发的时候将有怎样的行为。 RxJava 中的 Observer 接口的实现方式:

  1. Observer<String> observer = new Observer<String>() {
  2. @Override
  3. public void onNext(String s) {
  4. Log.d(tag, "Item: " + s);
  5. }
  6. @Override
  7. public void onCompleted() {
  8. Log.d(tag, "Completed!");
  9. }
  10. @Override
  11. public void onError(Throwable e) {
  12. Log.d(tag, "Error!");
  13. }
  14. };

除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。 Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的:

  1. Subscriber<String> subscriber = new Subscriber<String>() {
  2. @Override
  3. public void onNext(String s) {
  4. Log.d(tag, "Item: " + s);
  5. }
  6. @Override
  7. public void onCompleted() {
  8. Log.d(tag, "Completed!");
  9. }
  10. @Override
  11. public void onError(Throwable e) {
  12. Log.d(tag, "Error!");
  13. }
  14. };

不仅基本使用方式一样,实质上,在
RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber
再使用。所以如果你只想使用基本功能,选择 Observer 和 Subscriber 是完全一样的。它们的区别对于使用者来说主要有两点:
onStart(): 这是
Subscriber 增加的方法。它会在 subscribe
刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行),
onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用
doOnSubscribe() 方法,具体可以在后面的文中看到。
unsubscribe(): 这是
Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber
将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe()
这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber
的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如
onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。

2) 创建 Observable
Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:

  1. Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
  2. @Override
  3. public void call(Subscriber<? super String> subscriber) {
  4. subscriber.onNext("Hello");
  5. subscriber.onNext("Hi");
  6. subscriber.onNext("Aloha");
  7. subscriber.onCompleted();
  8. }
  9. });

可以看到,这里传入了一个
OnSubscribe 对象作为参数。OnSubscribe 会被存储在返回的 Observable 对象中,它的作用相当于一个计划表,当
Observable 被订阅的时候,OnSubscribe 的 call()
方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者Subscriber 将会被调用三次 onNext() 和一次
onCompleted())。这样,由被观察者调用了观察者的回调方法,就实现了由被观察者向观察者的事件传递,即观察者模式

create() 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列,例如:
just(T...): 将传入的参数依次发送出来。

  1. Observable observable = Observable.just("Hello", "Hi", "Aloha");
  2. // 将会依次调用:
  3. // onNext("Hello");
  4. // onNext("Hi");
  5. // onNext("Aloha");
  6. // onCompleted();

from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。

  1. String[] words = {"Hello", "Hi", "Aloha"};
  2. Observable observable = Observable.from(words);
  3. // 将会依次调用:
  4. // onNext("Hello");
  5. // onNext("Hi");
  6. // onNext("Aloha");
  7. // onCompleted();

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

3) Subscribe (订阅)
创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:

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

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

  1. // 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
  2. // 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
  3. public Subscription subscribe(Subscriber subscriber) {
  4. subscriber.onStart();
  5. onSubscribe.call(subscriber);
  6. return subscriber;
  7. }

可以看到,subscriber() 做了3件事:
1.调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
2.调用 Observable 中的 OnSubscribe.call(Subscriber)
。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable
并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
3.将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().

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

  1. Action1<String> onNextAction = new Action1<String>() {
  2. // onNext()
  3. @Override
  4. public void call(String s) {
  5. Log.d(tag, s);
  6. }
  7. };
  8. Action1<Throwable> onErrorAction = new Action1<Throwable>() {
  9. // onError()
  10. @Override
  11. public void call(Throwable throwable) {
  12. // Error handling
  13. }
  14. };
  15. Action0 onCompletedAction = new Action0() {
  16. // onCompleted()
  17. @Override
  18. public void call() {
  19. Log.d(tag, "completed");
  20. }
  21. };
  22. // 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
  23. observable.subscribe(onNextAction);
  24. // 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
  25. observable.subscribe(onNextAction, onErrorAction);
  26. // 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
  27. 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) 的,它们可以被用以包装不同的无返回值的方法。

4) 场景示例

下面举两个例子:
a. 打印字符串数组
将字符串数组 names 中的所有字符串依次打印出来:

  1. String[] names = ...;
  2. Observable.from(names)
  3. .subscribe(new Action1<String>() {
  4. @Override
  5. public void call(String name) {
  6. Log.d(tag, name);
  7. }
  8. });

b. 由 id 取得图片并显示
由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的时候打印 Toast 报错:

  1. int drawableRes = ...;
  2. ImageView imageView = ...;
  3. Observable.create(new OnSubscribe<Drawable>() {
  4. @Override
  5. public void call(Subscriber<? super Drawable> subscriber) {
  6. Drawable drawable = getTheme().getDrawable(drawableRes));
  7. subscriber.onNext(drawable);
  8. subscriber.onCompleted();
  9. }
  10. }).subscribe(new Observer<Drawable>() {
  11. @Override
  12. public void onNext(Drawable drawable) {
  13. imageView.setImageDrawable(drawable);
  14. }
  15. @Override
  16. public void onCompleted() {
  17. }
  18. @Override
  19. public void onError(Throwable e) {
  20. Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
  21. }
  22. });

正如上面两个例子这样,创建出 Observable 和 Subscriber ,再用 subscribe() 将它们串起来,一次 RxJava 的基本使用就完成了。非常简单。

注意:
RxJava
的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于
RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler 。

线程控制 —— Scheduler (一)
前言:

在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。

1) Scheduler 的 API (一)
在RxJava 中,Scheduler ——调度器,相当于线程控制器,RxJava 通过它来指定每一段代码应该运行在什么样的线程。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。
另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。
有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。 *
subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe
被激活时所处的线程。或者叫做事件产生的线程。 * observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。

代码来理解上面的文字叙述:

  1. Observable.just(1, 2, 3, 4)
  2. .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
  3. .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
  4. .subscribe(new Action1<Integer>() {
  5. @Override
  6. public void call(Integer number) {
  7. Log.d(tag, "number:" + number);
  8. }
  9. });

上面这段代码中,由于
subscribeOn(Schedulers.io()) 的指定,被创建的事件的内容 1、2、3、4 将会在 IO 线程发出;而由于
observeOn(AndroidScheculers.mainThread()) 的指定,因此 subscriber 数字的打印将发生在主线程
。事实上,这种在 subscribe() 之前写上两句 subscribeOn(Scheduler.io()) 和
observeOn(AndroidSchedulers.mainThread())
的使用方式非常常见,它适用于多数的 『后台线程取数据,主线程显示』的程序策略。

而前面提到的由图片 id 取得图片并显示的例子,如果也加上这两句:

  1. int drawableRes = ...;
  2. ImageView imageView = ...;
  3. Observable.create(new OnSubscribe<Drawable>() {
  4. @Override
  5. public void call(Subscriber<? super Drawable> subscriber) {
  6. Drawable drawable = getTheme().getDrawable(drawableRes));
  7. subscriber.onNext(drawable);
  8. subscriber.onCompleted();
  9. }
  10. })
  11. .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
  12. .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
  13. .subscribe(new Observer<Drawable>() {
  14. @Override
  15. public void onNext(Drawable drawable) {
  16. imageView.setImageDrawable(drawable);
  17. }
  18. @Override
  19. public void onCompleted() {
  20. }
  21. @Override
  22. public void onError(Throwable e) {
  23. Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
  24. }
  25. });

那么,加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。

2) Scheduler 的原理 (一)
RxJava 的 Scheduler API 很方便,也很神奇(加了一句话就把线程切换了,怎么做到的?而且 subscribe()
不是最外层直接调用的方法吗,它竟然也能被指定线程?)。然而 Scheduler
的原理需要放在后面讲,因为它的原理是以下一节《变换》的原理作为基础的。
好吧这一节其实我屁也没说,只是为了让你安心,让你知道我不是忘了讲原理,而是把它放在了更合适的地方。

变换
RxJava 提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava 真是太好用了』的最大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。概念说着总是模糊难懂的,来看 API。
1) API
首先看一个 map() 的例子:

  1. Observable.just("images/logo.png") // 输入类型 String
  2. .map(new Func1<String, Bitmap>() {
  3. @Override
  4. public Bitmap call(String filePath) { // 参数类型 String
  5. return getBitmapFromPath(filePath); // 返回类型 Bitmap
  6. }
  7. })
  8. .subscribe(new Action1<Bitmap>() {
  9. @Override
  10. public void call(Bitmap bitmap) { // 参数类型 Bitmap
  11. showBitmap(bitmap);
  12. }
  13. });

这里出现了一个叫做
Func1 的类。它和 Action1 非常相似,也是 RxJava 的一个接口,用于包装含有一个参数的方法。 Func1 和 Action
的区别在于, Func1 包装的是有返回值的方法。另外,和 ActionX 一样, FuncX 也有多个,用于不同参数个数的方法。FuncX 和
ActionX 的区别在 FuncX 包装的是有返回值的方法。
可以看到,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由
String 转为了 Bitmap。这种直接变换对象并返回的,是最常见的也最容易理解的变换。不过 RxJava
的变换远不止这样,它不仅可以针对事件对象,还可以针对整个事件队列,这使得 RxJava 变得非常灵活。我列举几个常用的变换:
map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava 最常用的变换。 map() 的示意图:
flatMap(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:

  1. Student[] students = ...;
  2. Subscriber<String> subscriber = new Subscriber<String>() {
  3. @Override
  4. public void onNext(String name) {
  5. Log.d(tag, name);
  6. }
  7. ...
  8. };
  9. Observable.from(students)
  10. .map(new Func1<Student, String>() {
  11. @Override
  12. public String call(Student student) {
  13. return student.getName();
  14. }
  15. })
  16. .subscribe(subscriber);

很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:

  1. Student[] students = ...;
  2. Subscriber<Student> subscriber = new Subscriber<Student>() {
  3. @Override
  4. public void onNext(Student student) {
  5. List<Course> courses = student.getCourses();
  6. for (int i = 0; i < courses.size(); i++) {
  7. Course course = courses.get(i);
  8. Log.d(tag, course.getName());
  9. }
  10. }
  11. ...
  12. };
  13. Observable.from(students)
  14. .subscribe(subscriber);

依然很简单。那么如果我不想在
Subscriber 中使用 for 循环,而是希望 Subscriber 中直接传入单个的 Course 对象呢(这对于代码复用很重要)?用
map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个
Course 呢?
这个时候,就需要用 flatMap() 了:

  1. Student[] students = ...;
  2. Subscriber<Course> subscriber = new Subscriber<Course>() {
  3. @Override
  4. public void onNext(Course course) {
  5. Log.d(tag, course.getName());
  6. }
  7. ...
  8. };
  9. Observable.from(students)
  10. .flatMap(new Func1<Student, Observable<Course>>() {
  11. @Override
  12. public Observable<Course> call(Student student) {
  13. return Observable.from(student.getCourses());
  14. }
  15. })
  16. .subscribe(subscriber);

从上面的代码可以看出,
flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是,
flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber
的回调方法中。 flatMap() 的原理是这样的:1. 使用传入的事件对象创建一个 Observable 对象;2. 并不发送这个
Observable, 而是将它激活,于是它开始发送事件;3.
每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable
负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable
将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

扩展:由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求。示例代码(Retrofit + RxJava):

  1. networkClient.token() // 返回 Observable<String>,在订阅时请求 token,并在响应后发送 token
  2. .flatMap(new Func1<String, Observable<Messages>>() {
  3. @Override
  4. public Observable<Messages> call(String token) {
  5. // 返回 Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
  6. return networkClient.messages();
  7. }
  8. })
  9. .subscribe(new Action1<Messages>() {
  10. @Override
  11. public void call(Messages messages) {
  12. // 处理显示消息列表
  13. showMessages(messages);
  14. }
  15. });

传统的嵌套请求需要使用嵌套的 Callback 来实现。而通过 flatMap() ,可以把嵌套的请求写在一条链中,从而保持程序逻辑的清晰。
throttleFirst(): 在每次事件触发后的一定时间间隔内丢弃新的事件。常用作去抖动过滤,例如按钮的点击监听器:
RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释 .throttleFirst(500,
TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms .subscribe(subscriber);
妈妈再也不怕我的用户手抖点开两个重复的界面啦。
此外, RxJava 还提供很多便捷的方法来实现事件序列的变换,这里就不一一举例了。

2) 变换的原理:lift()
这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在 RxJava 的内部,它们是基于同一个基础的变换方法: lift(Operator)。首先看一下 lift() 的内部实现(仅核心代码):
// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。

  1. public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
  2. return Observable.create(new OnSubscribe<R>() {
  3. @Override
  4. public void call(Subscriber subscriber) {
  5. Subscriber newSubscriber = operator.call(subscriber);
  6. newSubscriber.onStart();
  7. onSubscribe.call(newSubscriber);
  8. }
  9. });
  10. }

这段代码很有意思:它生成了一个新的
Observable 并返回,而且创建新 Observable 所用的参数 OnSubscribe 的回调方法 call()
中的实现竟然看起来和前面讲过的 Observable.subscribe() 一样!然而它们并不一样哟~不一样的地方关键就在于第二行
onSubscribe.call(subscriber) 中的 onSubscribe
所指代的对象不同(高能预警:接下来的几句话可能会导致身体的严重不适)——
subscribe() 中这句话的 onSubscribe 指的是 Observable 中的 onSubscribe 对象,这个没有问题,但是 lift() 之后的情况就复杂了点。
当含有 lift() 时: 
1.lift() 创建了一个 Observable 后,加上之前的原始 Observable,已经有两个 Observable 了; 
2.而同样地,新 Observable 里的新 OnSubscribe 加上之前的原始 Observable 中的原始 OnSubscribe,也就有了两个 OnSubscribe; 
3.当用户调用经过 lift() 后的 Observable 的 subscribe() 的时候,使用的是 lift() 所返回的新的
Observable ,于是它所触发的 onSubscribe.call(subscriber),也是用的新 Observable 中的新
OnSubscribe,即在 lift() 中生成的那个 OnSubscribe; 
4.而这个新 OnSubscribe 的 call() 方法中的 onSubscribe ,就是指的原始 Observable 中的原始
OnSubscribe ,在这个 call() 方法里,新 OnSubscribe 利用 operator.call(subscriber)
生成了一个新的 Subscriber(Operator 就是在这里,通过自己的 call() 方法将新 Subscriber 和原始
Subscriber 进行关联,并插入自己的『变换』代码以实现变换),然后利用这个新 Subscriber
向原始 Observable 进行订阅。 
这样就实现了 lift() 过程,有点像一种代理机制,通过事件拦截和处理实现事件序列的变换。
精简掉细节的话,也可以这么说:在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的
Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给
Subscriber。
举一个具体的 Operator 的实现。下面这是一个将事件中的 Integer 对象转换成 String 的例子,仅供参考:

  1. observable.lift(new Observable.Operator<String, Integer>() {
  2. @Override
  3. public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {
  4. // 将事件序列中的 Integer 对象转换为 String 对象
  5. return new Subscriber<Integer>() {
  6. @Override
  7. public void onNext(Integer integer) {
  8. subscriber.onNext("" + integer);
  9. }
  10. @Override
  11. public void onCompleted() {
  12. subscriber.onCompleted();
  13. }
  14. @Override
  15. public void onError(Throwable e) {
  16. subscriber.onError(e);
  17. }
  18. };
  19. }
  20. });

3) compose: 对 Observable 整体的变换
除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。它和 lift()
的区别在于, lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable
自身进行变换。举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:

  1. observable1
  2. .lift1()
  3. .lift2()
  4. .lift3()
  5. .lift4()
  6. .subscribe(subscriber1);
  7. observable2
  8. .lift1()
  9. .lift2()
  10. .lift3()
  11. .lift4()
  12. .subscribe(subscriber2);
  13. observable3
  14. .lift1()
  15. .lift2()
  16. .lift3()
  17. .lift4()
  18. .subscribe(subscriber3);
  19. observable4
  20. .lift1()
  21. .lift2()
  22. .lift3()
  23. .lift4()
  24. .subscribe(subscriber1);

你觉得这样太不软件工程了,于是你改成了这样:

  1. private Observable liftAll(Observable observable) {
  2. return observable
  3. .lift1()
  4. .lift2()
  5. .lift3()
  6. .lift4();
  7. }
  8. ...
  9. liftAll(observable1).subscribe(subscriber1);
  10. liftAll(observable2).subscribe(subscriber2);
  11. liftAll(observable3).subscribe(subscriber3);
  12. liftAll(observable4).subscribe(subscriber4);

可读性、可维护性都提高了。可是 Observable 被一个方法包起来,这种方式对于 Observale 的灵活性似乎还是增添了那么点限制。怎么办?这个时候,就应该用 compose() 来解决了:

  1. public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
  2. @Override
  3. public Observable<String> call(Observable<Integer> observable) {
  4. return observable
  5. .lift1()
  6. .lift2()
  7. .lift3()
  8. .lift4();
  9. }
  10. }
  11. ...
  12. Transformer liftAll = new LiftAllTransformer();
  13. observable1.compose(liftAll).subscribe(subscriber1);
  14. observable2.compose(liftAll).subscribe(subscriber2);
  15. observable3.compose(liftAll).subscribe(subscriber3);
  16. observable4.compose(liftAll).subscribe(subscriber4);

像上面这样,使用 compose() 方法,Observable 可以利用传入的 Transformer 对象的 call 方法直接对自身进行处理,也就不必被包在方法的里面了。
compose() 的原理比较简单,不附图喽。

线程控制:Scheduler (二)
除了灵活的变换,RxJava 另一个牛逼的地方,就是线程的自由控制。
1) Scheduler 的 API (二)
前面讲到了,可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。可是在了解了
map() flatMap() 等变换方法后,有些好事的(其实就是当初刚接触 RxJava 时的我)就问了:能不能多切换几次线程?
答案是:能。因为 observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber
并不是(严格说应该为『不一定是』,但这里不妨理解为『不是』)subscribe() 参数中的 Subscriber ,而是
observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber
。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次
observeOn()
即可。上代码:

  1. Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
  2. .subscribeOn(Schedulers.io())
  3. .observeOn(Schedulers.newThread())
  4. .map(mapOperator) // 新线程,由 observeOn() 指定
  5. .observeOn(Schedulers.io())
  6. .map(mapOperator2) // IO 线程,由 observeOn() 指定
  7. .observeOn(AndroidSchedulers.mainThread)
  8. .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

如上,通过 observeOn() 的多次调用,程序实现了线程的多次切换。
不过,不同于 observeOn() , subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。
又有好事的(其实还是当初的我)问了:如果我非要调用多次 subscribeOn() 呢?会有什么效果?
这个问题先放着,我们还是从 RxJava 线程控制的原理说起吧。
2) Scheduler 的原理(二)
其实, subscribeOn() 和 observeOn() 的内部实现,也是用的 lift()。具体看图(不同颜色的箭头表示不同的线程):
从图中可以看出,subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的 "schedule..."
部位)。不同的是, 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() 所指定的线程。
示例代码:

  1. Observable.create(onSubscribe)
  2. .subscribeOn(Schedulers.io())
  3. .doOnSubscribe(new Action0() {
  4. @Override
  5. public void call() {
  6. progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
  7. }
  8. })
  9. .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
  10. .observeOn(AndroidSchedulers.mainThread())
  11. .subscribe(subscriber);

如上,在 doOnSubscribe()的后面跟一个 subscribeOn() ,就能指定准备工作的线程了

深入浅出RxJava就这一篇就够了的更多相关文章

  1. 深入浅出RxJava

    深入浅出RxJava(一:基础篇) 深入浅出RxJava(二:操作符) 深入浅出RxJava三--响应式的好处 深入浅出RxJava四-在Android中使用响应式编程 RxJava 到底是什么? 一 ...

  2. 如果这样来理解HTTPS,一篇就够了!

    1.前言 可能有初学者会问,即时通讯应用的通信安全,不就是对Socket长连接进行SSL/TLS加密这些知识吗,干吗要理解HTTPS协议呢. 这其实是个误解:当今主流的移动端IM数据通信,总结下来无外 ...

  3. React入门看这篇就够了

    摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所有. React 背景介绍 React 入门实例教程 React 起源于 ...

  4. [转]React入门看这篇就够了

    摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所有. React 背景介绍 React 入门实例教程 React 起源于 ...

  5. [原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)

    原文:[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇) .NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇) 前言:接着上篇来. 系列文章链接: [ ...

  6. 关于 Docker 镜像的操作,看完这篇就够啦 !(下)

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

  7. ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  8. Ajax原理一篇就够了

    Ajax原理一篇就够了 一.什么是Ajax Ajax(Asynchronous JavaScript and XML的缩写)是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助.简 ...

  9. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...

随机推荐

  1. win7下安装 LINUX虚拟机

    文件名: VMware-workstation-full-10.0.6-2700073.exe 百度云共享链接: pan.baidu.com/s/1o6McGmI VMware workstation ...

  2. gitlab和github的区别

          先说一下相同点,二者都是基于web的Git仓库,在很大程度上GitLab是仿照GitHub来做的,它们都提供了分享开源项目的平台,为开发团队提供了存储.分享.发布和合作开发项目的中心化云存 ...

  3. Linux下安装rz、sz命令(文件上传下载)

    yum install -y lrzsz 说明:rz命令本地上传文件到服务器:sz命令发送文件到本地.

  4. 使用JAXB读写xml

    1.注解 在Java模型中的创建与 xml 文件对应的节点和属性需要用注解来表示 @XmlRootElement 作用:将一个Java类映射为一段XML的根节点参数:    name  定义这个根节点 ...

  5. 树莓派与windows互传文件

    这是 meelo 原创的 玩转树莓派 系列文章 安装WinSCP 登录即可在左右两侧分别显示windows和树莓派中的文件 只需将文件从一侧拖到另一侧即可开始文件的传送

  6. http.pieplining

    默认情况下http协议中每个传输层连接只能承载一个http请求和响应,然后结束. HTTP是一个简单的协议.客户进程建立一条同服务器进程的 T C P连接,然后发出请求并读取服务器进程的响应.服务器进 ...

  7. vue-music 关于Search(搜索页面)-- 搜索结果优化

    搜索结果 列表点击跳转到相应的歌手详情页或者 歌曲页面,通过子路由跳转,和singer 组件一样 在suggest.vue 组件判断如果点击的是歌手,则new 一个歌手对象,通过这个对象的id 属性值 ...

  8. [js] 数据结构

    var dic = {"127.0.0.1":{"1440":[["keyx","keyy","clix&qu ...

  9. 转:libFuzzer Tutorial(libFuzzer教程)

    转:https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md 本文在Ubuntu16下 ...

  10. C#将String传入C++的char*

    C++的函数参数列表中包含一个char*的输出型参数,然而在C#调用该dll时候,会自动将函数的中的char*参数“翻译”为sbyte*, 使用了各种方法都不能调用函数,主要是不能合适的转换为sbyt ...