RxJava之深入浅出进阶详解

来源:互联网 发布:国际顶级域名com 编辑:程序博客网 时间:2024/06/07 14:19

转载自:http://blog.csdn.net/caihongdao123/article/details/51897793

前言:

看了前一章基础,初步了解Rxjava的朋友, RxJava之基础入门

第一次接触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 代码:

[java] view plain copy
  1. compile 'io.reactivex:rxjava:1.0.14'   
  2. compile 'io.reactivex:rxandroid:1.0.1'   

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

[java] view plain copy
  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的代码:
[java] view plain copy
  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 化的预览,这将让你更加清晰地看到程序逻辑:

[java] view plain copy
  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 接口的实现方式:

[java] view plain copy
  1. Observer<String> observer = new Observer<String>() {  
  2.     @Override  
  3.     public void onNext(String s) {  
  4.         Log.d(tag, "Item: " + s);  
  5.     }  
  6.   
  7.     @Override  
  8.     public void onCompleted() {  
  9.         Log.d(tag, "Completed!");  
  10.     }  
  11.   
  12.     @Override  
  13.     public void onError(Throwable e) {  
  14.         Log.d(tag, "Error!");  
  15.     }  
  16. };  
除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。 Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的:
[java] view plain copy
  1. Subscriber<String> subscriber = new Subscriber<String>() {  
  2.     @Override  
  3.     public void onNext(String s) {  
  4.         Log.d(tag, "Item: " + s);  
  5.     }  
  6.   
  7.     @Override  
  8.     public void onCompleted() {  
  9.         Log.d(tag, "Completed!");  
  10.     }  
  11.   
  12.     @Override  
  13.     public void onError(Throwable e) {  
  14.         Log.d(tag, "Error!");  
  15.     }  
  16. };  
不仅基本使用方式一样,实质上,在 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 ,并为它定义事件触发规则:
[java] view plain copy
  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...): 将传入的参数依次发送出来。

[java] view plain copy
  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 拆分成具体对象后,依次发送出来。
[java] view plain copy
  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() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:
[java] view plain copy
  1. observable.subscribe(observer);  
  2. // 或者:  
  3. observable.subscribe(subscriber);  
Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):
[java] view plain copy
  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 。形式如下:
[java] view plain copy
  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.   
  23. // 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()  
  24. observable.subscribe(onNextAction);  
  25. // 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()  
  26. observable.subscribe(onNextAction, onErrorAction);  
  27. // 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()  
  28. 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 中的所有字符串依次打印出来:

[java] view plain copy
  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 报错:
[java] view plain copy
  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.   
  16.     @Override  
  17.     public void onCompleted() {  
  18.     }  
  19.   
  20.     @Override  
  21.     public void onError(Throwable e) {  
  22.         Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();  
  23.     }  
  24. });  
正如上面两个例子这样,创建出 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 所运行在的线程。或者叫做事件消费的线程。

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

[java] view plain copy
  1. Observable.just(1234)  
  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 取得图片并显示的例子,如果也加上这两句:
[java] view plain copy
  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() 的例子:
[java] view plain copy
  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(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
[java] view plain copy
  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);  
很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:
[java] view plain copy
  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() 了:
[java] view plain copy
  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):
[java] view plain copy
  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 仓库下载。
[java] view plain copy
  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 的例子,仅供参考:
[java] view plain copy
  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.   
  11.   
  12.             @Override  
  13.             public void onCompleted() {  
  14.                 subscriber.onCompleted();  
  15.             }  
  16.   
  17.   
  18.             @Override  
  19.             public void onError(Throwable e) {  
  20.                 subscriber.onError(e);  
  21.             }  
  22.         };  
  23.     }  
  24. });  

3) compose: 对 Observable 整体的变换
除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。它和 lift() 的区别在于, lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable 自身进行变换。举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:
[java] view plain copy
  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);  
你觉得这样太不软件工程了,于是你改成了这样:
[java] view plain copy
  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() 来解决了:
[java] view plain copy
  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() 即可。上代码:
[java] view plain copy
  1. Observable.just(1234// 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() 所指定的线程。
示例代码:
[java] view plain copy
  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() ,就能指定准备工作的线程了。
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 欧陆风云4破产后怎么办 车被别人喷了漆怎么办 龙分期绑银行卡维护钱还不上怎么办 去维和要是伤了怎么办 头盔镜片刮花了怎么办 小牛u1钥匙丢了怎么办 非牛顿流体干了怎么办 刺激战场0信誉分怎么办 假如非牛顿干了怎么办? 退出id后照片了怎么办 小麦收割机麦糠有籽粒怎么办 在家带娃没钱花怎么办 看3d电影近视的怎么办 摩托车电瓶没电了怎么办 踏板摩托车电瓶没电了怎么办 摩托车离合油没了怎么办 论文数据计算错误该怎么办 脚被擦破皮了痛怎么办 脚撞到了很疼怎么办 腿不小心磕青了怎么办 木制椅子腿断了怎么办 实木椅子腿断了怎么办 铁质椅子腿断了怎么办 大套摩托车查车怎么办 摩托车本扣12分怎么办 蓝牙耳机上面的软套掉了怎么办 工作不想干了怎么办啊 浴室门玻璃坏了怎么办 电梯顶层安全安全距离不合格怎么办 制冰机有些孔不出水怎么办 制冰机排水孔堵住了怎么办 点我达装备抽查怎么办 空军常服大檐帽有一点变形怎么办 一般纳税人没有进项发票怎么办 汽车购买发票丢了怎么办 购买房子的发票丢了怎么办 购买的二手房发票丢失怎么办 购买空调时的发票丢失怎么办 发票购票本丢了怎么办 留学中介费贵了怎么办 车祸伤者不肯出院怎么办