RxJava
GitHub 链接:
https://github.com/ReactiveX/RxJava
https://github.com/ReactiveX/RxAndroid
引入依赖:
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 好在哪?就好在简洁,好在那把什么复杂逻辑都能穿成一条线的简洁。
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和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!");
}
};
Subscriber
除了 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 是完全一样的。
Observer vs 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 ,并为它定义事件触发规则:
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
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) 的内部实现:
注意:这不是 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() 方法执行的时候。
3. 将传入的 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 。
3. 线程控制 —— 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。
AndroidSchedulers.mainThread()
它指定的操作将在 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);
}
});
上面这段代码中,由于 subscribeOn(Schedulers.io()) 的指定,被创建的事件的内容 1、2、3、4 将会在IO 线程发出;而由于 observeOn(AndroidScheculers.mainThread())的指定,因此 subscriber 数字的打印将发生在主线程。
事实上,这种在 subscribe() 之前写上两句 subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常见,它适用于多数的『后台线程取数据,主线程显示』的程序策略。
而前面提到的由图片 id 取得图片并显示的例子,如果也加上这两句:
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();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.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();
}
});
那么,加载图片将会发生在 IO线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。
4. 变换
RxJava提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava真是太好用了』的最大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。概念说着总是模糊难懂的,来看
API。
1) API
map()
首先看一个 map() 的例子:
Observable.just("images/logo.png") // 输入类型 String
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) { // 参数类型 String
return getBitmapFromPath(filePath); // 返回类型 Bitmap
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) { // 参数类型 Bitmap
showBitmap(bitmap);
}
});
这里出现了一个叫做 Func1 的类。它和 Action1 非常相似,也是 RxJava的一个接口,用于包装含有一个参数的方法。
Func1 和 Action 的区别在于, Func1 包装的是有返回值的方法。
另外,和 ActionX 一样, FuncX 也有多个,用于不同参数个数的方法。FuncX和 ActionX 的区别在 FuncX 包装的是有返回值的方法。
可以看到,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由 String转为了 Bitmap。这种直接变换对象并返回的,是最常见的也最容易理解的变换。
- map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava最常用的变换。
map() 的示意图:
不过RxJava 的变换远不止这样,它不仅可以针对事件对象,还可以针对整个事件队列,这使得RxJava 变得非常灵活。
flatMap()
首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
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();
}
})
.subscribe(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)
.subscribe(subscriber);
依然很简单。那么如果我不想在 Subscriber 中使用 for循环,而是希望 Subscriber 中直接传入单个的 Course 对象呢(这对于代码复用很重要)?
用 map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。
那怎么才能把一个*Student 转化成多个* Course 呢?这个时候,就需要用 flatMap() 了:
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);
从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。
但需要注意,和 map()不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。
flatMap() 原理:
- 使用传入的事件对象创建一个 Observable 对象;
- 并不发送这个 Observable,而是将它激活,于是它开始发送事件;
- 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。
这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的flat。
flatMap() 示意图:
扩展:由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求。示例代码(Retrofit + RxJava):
networkClient.token() // 返回 Observable<String>,在订阅时请求token,并在响应后发送 token
.flatMap(new Func1<String, Observable<Messages>>() {
@Override
public Observable<Messages> call(String token) {
// 返回Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
return networkClient.messages();
}
})
.subscribe(new Action1<Messages>() {
@Override
public void call(Messages messages) {
// 处理显示消息列表
showMessages(messages);
}
});
传统的嵌套请求需要使用嵌套的 Callback来实现。而通过 flatMap() ,可以把嵌套的请求写在一条链中,从而保持程序逻辑的清晰。
throttleFirst():
在每次事件触发后的一定时间间隔内丢弃新的事件。
常用作去抖动过滤,例如按钮的点击监听器:
RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释
.throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms
.subscribe(subscriber);//妈妈再也不怕我的用户手抖点开两个重复的界面啦。
此外, RxJava 还提供很多便捷的方法来实现事件序列的变换,这里就不一一举例了。
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 并返回,而且创建新 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。
如果你更喜欢具象思维,可以看图:
或者可以看动图:
两次和多次的 lift() 同理,如下图:
举一个具体的 Operator 的实现。下面这是一个将事件中的 Integer 对象转换成 String 的例子,仅供参考:
observable.lift(new Observable.Operator<String, Integer>() {
@Override
public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {
// 将事件序列中的 Integer 对象转换为 String 对象
return new Subscriber<Integer>() {
@Override
public void onNext(Integer integer) {
subscriber.onNext("" + integer);
}
@Override
public void onCompleted() {
subscriber.onCompleted();
}
@Override
public void onError(Throwable e) {
subscriber.onError(e);
}
};
}
});
讲述 lift() 的原理只是为了让你更好地了解 RxJava,从而可以更好地使用它。然而不管你是否理解了 lift() 的原理,RxJava都不建议开发者自定义 Operator 来直接使用 lift(),而是建议尽量使用已有的 lift() 包装方法(如 map() flatMap() 等)进行组合来实现需求,因为直接使用lift() 非常容易发生一些难以发现的错误。
3) compose: 对 Observable 整体的变换
除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。
它和 lift() 的区别在于, lift()是针对事件项和事件序列的,而 compose()是针对Observable自身进行变换。
举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:
observable1
.lift1()
.lift2()
.lift3()
.lift4()
.subscribe(subscriber1);
observable2
.lift1()
.lift2()
.lift3()
.lift4()
.subscribe(subscriber2);
observable3
.lift1()
.lift2()
.lift3()
.lift4()
.subscribe(subscriber3);
observable4
.lift1()
.lift2()
.lift3()
.lift4()
.subscribe(subscriber1);
你觉得这样太不软件工程了,于是你改成了这样:
private Observable liftAll (Observable observable){
return observable
.lift1()
.lift2()
.lift3()
.lift4();
}
...
liftAll(observable1).subscribe(subscriber1);
liftAll(observable2).subscribe(subscriber2);
liftAll(observable3).subscribe(subscriber3);
liftAll(observable4).subscribe(subscriber4);
可读性、可维护性都提高了。可是 Observable 被一个方法包起来,这种方式对于 Observale 的灵活性似乎还是增添了那么点限制。怎么办?这个时候,就应该用 compose() 来解决了:
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 (二)
除了灵活的变换,RxJava 另一个牛逼的地方,就是线程的自由控制。
1) Scheduler 的 API (二)
前面讲到了,可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。可是在了解了 map() flatMap() 等变换方法后,有些好事的(其实就是当初刚接触
RxJava 时的我)就问了:能不能多切换几次线程?
答案是:能。因为 observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 并不是(严格说应该为『不一定是』,但这里不妨理解为『不是』)subscribe() 参数中的 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() 的位置放在哪里都可以,但它是只能调用一次的。
又有好事的(其实还是当初的我)问了:如果我非要调用多次 subscribeOn() 呢?会有什么效果?
这个问题先放着,我们还是从 RxJava 线程控制的原理说起吧。
2) Scheduler 的原理(二)
其实, subscribeOn() 和 observeOn() 的内部实现,也是用的 lift()。具体看图(不同颜色的箭头表示不同的线程):
subscribeOn() 原理图:
observeOn() 原理图:
从图中可以看出,subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的”schedule…”部位)。
不同的是:
- subscribeOn()的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;
- observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。
最后,我用一张图来解释当多个 subscribeOn() 和 observeOn() 混合使用时,线程调度是怎么发生的(由于图中对象较多,相对于上面的图对结构做了一些简化调整):
图中共有 5处含有对事件的操作。由图中可以看出,①和②两处受第一个 subscribeOn() 影响,运行在红色线程;③和④处受第一个 observeOn() 的影响,运行在绿色线程;⑤处受第二个 onserveOn() 影响,运行在紫色线程;而第二个 subscribeOn() ,由于在通知过程中线程就被第一个 subscribeOn() 截断,因此对整个流程并没有任何影响。这里也就回答了前面的问题:当使用了多个 subscribeOn() 的时候,只有第一个 subscribeOn() 起作用。
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() ,就能指定准备工作的线程了。
RxJava 的适用场景和使用方式
1. 与 Retrofit 的结合
Retrofit 是 Square 的一个著名的网络请求库。没有用过 Retrofit的可以选择跳过这一小节也没关系,我举的每种场景都只是个例子,而且例子之间并无前后关联,只是个抛砖引玉的作用,所以你跳过这里看别的场景也可以的。
Retrofit 除了提供了传统的 Callback 形式的 API,还有 RxJava版本的 Observable 形式 API。下面我用对比的方式来介绍 Retrofit 的 RxJava 版 API和传统版本的区别。
以获取一个 User 对象的接口作为例子。
使用Retrofit 的传统API,你可以用这样的方式来定义请求:
@GET("/user")
public void getUser(@Query("userId") String userId, Callback<User> callback);
在程序的构建过程中, Retrofit会把自动把方法实现并生成代码,然后开发者就可以利用下面的方法来获取特定用户并处理响应:
getUser(userId, new Callback<User>() {
@Override
public void success(User user) {
userView.setUser(user);
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
而使用 RxJava 形式的 API,定义同样的请求是这样的:
@GET("/user")
public Observable<User> getUser(@Query("userId") String userId);
使用的时候是这样的:
getUser(userId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable error) {
// Error handling
...
}
});
看到区别了吗?
当 RxJava 形式的时候,Retrofit把请求封装进 Observable ,在请求结束后调用 onNext() 或在请求失败后调用 onError()。
对比来看, Callback 形式和 Observable 形式长得不太一样,但本质都差不多,而且在细节上 Observable 形式似乎还比 Callback形式要差点。那Retrofit 为什么还要提供 RxJava 的支持呢?
因为它好用啊!从这个例子看不出来是因为这只是最简单的情况。而一旦情景复杂起来, Callback 形式马上就会开始让人头疼。
比如:
假设这么一种情况:你的程序取到的 User 并不应该直接显示,而是需要先与数据库中的数据进行比对和修正后再显示。使用 Callback方式大概可以这么写:
getUser(userId, new Callback<User>() {
@Override
public void success(User user) {
processUser(user); // 尝试修正 User 数据
userView.setUser(user);
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
有问题吗?
很简便,但不要这样做。为什么?因为这样做会影响性能。数据库的操作很重,一次读写操作花费
10\~20ms是很常见的,这样的耗时很容易造成界面的卡顿。所以通常情况下,如果可以的话一定要避免在主线程中处理数据库。所以为了提升性能,这段代码可以优化一下:
getUser(userId, new Callback<User>() {
@Override
public void success(User user) {
new Thread() {
@Override
public void run() {
processUser(user); // 尝试修正 User 数据
runOnUiThread(new Runnable() { // 切回 UI 线程
@Override
public void run() {
userView.setUser(user);
}
});
}).start();
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
性能问题解决,但……这代码实在是太乱了,迷之缩进啊!杂乱的代码往往不仅仅是美观问题,因为代码越乱往往就越难读懂,而如果项目中充斥着杂乱的代码,无疑会降低代码的可读性,造成团队开发效率的降低和出错率的升高。
这时候,如果用 RxJava 的形式,就好办多了。 RxJava 形式的代码是这样的:
getUser(userId)
.doOnNext(new Action1<User>() {
@Override
public void call(User user) {
processUser(user);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable error) {
// Error handling
...
}
});
后台代码和前台代码全都写在一条链中,明显清晰了很多。
再举一个例子:假设 /user 接口并不能直接访问,而需要填入一个在线获取的 token ,代码应该怎么写?
Callback 方式,可以使用嵌套的 Callback:
@GET("/token")
public void getToken(Callback<String> callback);
@GET("/user")
public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback);
...
getToken(new Callback<String>() {
@Override
public void success(String token) {
getUser(token, userId, new Callback<User>() {
@Override
public void success(User user) {
userView.setUser(user);
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
});
倒是没有什么性能问题,可是迷之缩进毁一生,你懂我也懂,做过大项目的人应该更懂。
而使用 RxJava 的话,代码是这样的:
@GET("/token")
public Observable<String> getToken();
@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);
...
getToken()
.flatMap(new Func1<String, Observable<User>>() {
@Override
public Observable<User> onNext(String token) {
return getUser(token, userId);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable error) {
// Error handling
...
}
});
用一个 flatMap() 就搞定了逻辑,依然是一条链。看着就很爽,是吧?
加上我写的一个 Sample 项目:
rengwuxian RxJava Samples
好,Retrofit 部分就到这里。
2. RxBinding
RxBinding 是 Jake Wharton的一个开源库,它提供了一套在 Android 平台上的基于 RxJava 的 Binding API。所谓Binding,就是类似设置 OnClickListener 、设置 TextWatcher 这样的注册绑定对象的API。
举个设置点击监听的例子。使用 RxBinding ,可以把事件监听用这样的方法来设置:
Button button = ...;
RxView.clickEvents(button) // 以 Observable 形式来反馈点击事件
.subscribe(new Action1<ViewClickEvent>() {
@Override
public void call(ViewClickEvent event) {
// Click handling
}
});
看起来除了形式变了没什么区别,实质上也是这样。甚至如果你看一下它的源码,你会发现它连实现都没什么惊喜:它的内部是直接用一个包裹着的 setOnClickListener() 来实现的。然而,仅仅这一个形式的改变,却恰好就是 RxBinding 的目的:扩展性。通过 RxBinding 把点击监听转换成 Observable 之后,就有了对它进行扩展的可能。扩展的方式有很多,根据需求而定。一个例子是前面提到过的 throttleFirst() ,用于去抖动,也就是消除手抖导致的快速连环点击:
RxView.clickEvents(button)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribe(clickAction);
如果想对 RxBinding 有更多了解,可以去它的 GitHub项目 下面看看。
3. 各种异步操作
前面举的 Retrofit 和 RxBinding 的例子,是两个可以提供现成的 Observable 的库。而如果你有某些异步操作无法用这些库来自动生成 Observable,也完全可以自己写。例如数据库的读写、大图片的载入、文件压缩/解压等各种需要放在后台工作的耗时操作,都可以用RxJava 来实现,有了之前几章的例子,这里应该不用再举例了。
4. RxBus
RxBus 名字看起来像一个库,但它并不是一个库,而是一种模式,它的思想是使用 RxJava来实现了 EventBus ,而让你不再需要使用 Otto 或者 GreenRobot的 EventBus。至于什么是
RxBus,可以看这篇文章。顺便说一句,Flipboard已经用 RxBus 替换掉了 Otto ,目前为止没有不良反应。
操作符的使用
merge操作符,合并观察对象
List<String> list1 = new ArrayList<>() ;
List<String> list2 = new ArrayList<>() ;
list1.add( "1" ) ;
list1.add( "2" ) ;
list1.add( "3" ) ;
list2.add( "a" ) ;
list2.add( "b" ) ;
list2.add( "c" ) ;
Observable observable1 = Observable.from( list1 ) ;
Observable observable2 = Observable.from( list2 ) ;
//合并数据 先发送observable2的全部数据,然后发送 observable1的全部数据
Observable observable = Observable.merge( observable2 , observable1 ) ;
observable.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "rx-- " + o );
}
}) ;
运行结果
zip 操作符,合并多个观察对象的数据。并且允许 Func2()函数重新发送合并后的数据
List<String> list1 = new ArrayList<>() ;
List<String> list2 = new ArrayList<>() ;
list1.add( "1" ) ;
list1.add( "2" ) ;
list1.add( "3" ) ;
list2.add( "a" ) ;
list2.add( "b" ) ;
list2.add( "c" ) ;
list2.add( "d" ) ;
Observable observable1 = Observable.from( list1 ) ;
Observable observable2 = Observable.from( list2 ) ;
Observable observable3 = Observable.zip(observable1, observable2, new Func2<String , String , String >() {
@Override
public String call(String s1 , String s2 ) {
return s1 + s2 ;
}
}) ;
observable3.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "zip-- " + o );
}
}) ;
运行效果:从效果图上可以看出,合并两个的观察对象数据项应该是相等的;如果出现了数据项不等的情况,合并的数据项以最小数据队列为准。
scan累加器操作符
Observable observable = Observable.just( 1 , 2 , 3 , 4 , 5 ) ;
observable.scan(new Func2<Integer,Integer,Integer>() {
@Override
public Integer call(Integer o, Integer o2) {
return o + o2 ;
}
})
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "scan-- " + o );
}
}) ;
运行效果:
第一次发射得到1,作为结果与2相加;发射得到3,作为结果与3相加,以此类推,打印结果:
filter 过滤操作符的使用
Observable observable = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable.filter(new Func1<Integer , Boolean>() {
@Override
public Boolean call(Integer o) {
//数据大于4的时候才会被发送
return o > 4 ;
}
})
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "filter-- " + o );
}
}) ;
运行效果
消息数量过滤操作符的使用
take :取前n个数据
takeLast:取后n个数据
first 只发送第一个数据
last 只发送最后一个数据
skip() 跳过前n个数据发送后面的数据
skipLast() 跳过最后n个数据,发送前面的数据
//take 发送前3个数据
Observable observable = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable.take( 3 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "take-- " + o );
}
}) ;
//takeLast 发送最后三个数据
Observable observable2 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable2.takeLast( 3 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "takeLast-- " + o );
}
}) ;
//first 只发送第一个数据
Observable observable3 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable3.first()
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "first-- " + o );
}
}) ;
//last 只发送最后一个数据
Observable observable4 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable4.last()
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "last-- " + o );
}
}) ;
//skip() 跳过前2个数据发送后面的数据
Observable observable5 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable5.skip( 2 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "skip-- " + o );
}
}) ;
//skipLast() 跳过最后两个数据,发送前面的数据
Observable observable6 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable5.skipLast( 2 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "skipLast-- " + o );
}
}) ;
效果图
elementAt 、elementAtOrDefault发送数据序列中第n个数据
//elementAt() 发送数据序列中第n个数据 ,序列号从0开始
//如果该序号大于数据序列中的最大序列号,则会抛出异常,程序崩溃
//所以在用elementAt操作符的时候,要注意判断发送的数据序列号是否越界
Observable observable7 = Observable.just( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) ;
observable7.elementAt( 3 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "elementAt-- " + o );
}
}) ;
//elementAtOrDefault( int n , Object default ) 发送数据序列中第n个数据 ,序列号从0开始。
//如果序列中没有该序列号,则发送默认值
Observable observable9 = Observable.just( 1 , 2 , 3 , 4 , 5 ) ;
observable9.elementAtOrDefault( 8 , 666 )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "elementAtOrDefault-- " + o );
}
}) ;
运行结果
startWith() 插入数据
//插入普通数据
//startWith 数据序列的开头插入一条指定的项 , 最多插入9条数据
Observable observable = Observable.just( "aa" , "bb" , "cc" ) ;
observable
.startWith( "11" , "22" )
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "startWith-- " + o );
}
}) ;
//插入Observable对象
List<String> list = new ArrayList<>() ;
list.add( "ww" ) ;
list.add( "tt" ) ;
observable.startWith( Observable.from( list ))
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "startWith2 -- " + o );
}
}) ;
运行结果
delay操作符,延迟数据发送
Observable<String> observable = Observable.just( "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" ) ;
//延迟数据发射的时间,仅仅延时一次,也就是发射第一个数据前延时。发射后面的数据不延时
observable.delay( 3 , TimeUnit.SECONDS ) //延迟3秒钟
.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println("delay-- " + o);
}
}) ;
Timer 延时操作符的使用
使用场景:xx秒后,执行xx
//5秒后输出 hello world , 然后显示一张图片
Observable.timer( 5 , TimeUnit.SECONDS )
.observeOn(AndroidSchedulers.mainThread() )
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println( "timer--hello world " + aLong );
findViewById( R.id.image).setVisibility(View.VISIBLE );
}
}) ;
timer 返回一个 Observable , 它在延迟一段给定的时间后发射一个简单的数字0
timer 操作符默认在computation调度器上执行,当然也可以用 Scheduler在定义执行的线程。
delay vs timer
- 相同点:delay 、 timer 都是延时操作符。
- 不同点:
delay延时一次,延时完成后,可以连续发射多个数据。
timer延时一次,延时完成后,只发射一次数据。
interval 轮询操作符,循环发送数据,数据从0开始递增
public class IntervalActivity extends AppCompatActivity {
Subscription subscription ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_interval);
//参数一:延迟时间 参数二:间隔时间 参数三:时间颗粒度
Observable observable = Observable.interval(3000, 3000, TimeUnit.MILLISECONDS) ;
subscription = observable.subscribe(new Action1() {
@Override
public void call(Object o) {
System.out.println( "interval- " + o );
}
}) ;
}
@Override
protected void onDestroy() {
super.onDestroy();
if ( subscription != null ){
subscription.unsubscribe();
}
}
}
doOnNext() 操作符,在每次 OnNext() 方法被调用前执行
使用场景:从网络请求数据,在数据被展示前,缓存到本地
Observable observable = Observable.just( "1" , "2" , "3" , "4" ) ;
observable.doOnNext(new Action1() {
@Override
public void call(Object o) {
System.out.println( "doOnNext--缓存数据" + o );
}
})
.subscribe(new Observer() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Object o) {
System.out.println( "onNext--" + o );
}
}) ;
Buffer 操作符
- Buffer( int n ) 把n个数据打成一个list包,然后再次发送。
- Buffer( int n , int skip) 把n个数据打成一个list包,然后跳过第skip个数据。
使用场景:一个按钮每点击3次,弹出一个toast
List<String> list = new ArrayList<>();
for (int i = 1; i < 10; i++) {
list.add("" + i);
}
Observable<String> observable = Observable.from(list);
observable
.buffer(2) //把每两个数据为一组打成一个包,然后发送
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
System.out.println( "buffer---------------" );
Observable.from( strings ).subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println( "buffer data --" + s );
}
}) ;
}
});
例子2:
//第1、2 个数据打成一个数据包,跳过第三个数据 ; 第4、5个数据打成一个包,跳过第6个数据
observable.buffer( 2 , 3 ) //把每两个数据为一组打成一个包,然后发送。第三个数据跳过去
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
System.out.println( "buffer22---------------" );
Observable.from( strings ).subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println( "buffer22 data --" + s );
}
}) ;
}
}) ;
throttleFirst 操作符在一段时间内,只取第一个事件,然后其他事件都丢弃。
使用场景:
1、button按钮防抖操作,防连续点击
2、百度关键词联想,在一段时间内只联想一次,防止频繁请求服务器
Observable.interval( 1 , TimeUnit.SECONDS)
.throttleFirst( 3 , TimeUnit.SECONDS )
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println( "throttleFirst--" + aLong );
}
}) ;
**这段代码,是循环发送数据,每秒发送一个。**throttleFirst( 3 , TimeUnit.SECONDS
) 在3秒内只取第一个事件,其他的事件丢弃。
运行结果
14、distinct 过滤重复的数据
List<String> list = new ArrayList<>() ;
list.add( "1" ) ;
list.add( "2" ) ;
list.add( "1" ) ;
list.add( "3" ) ;
list.add( "4" ) ;
list.add( "2" ) ;
list.add( "1" ) ;
list.add( "1" ) ;
Observable.from( list )
.distinct()
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println( "distinct--" + s );
}
}) ;
从结果可以看出,重复的数据已经被过滤掉了
distinctUntilChanged() 过滤连续重复的数据
List<String> list = new ArrayList<>() ;
list.add( "1" ) ;
list.add( "2" ) ;
list.add( "1" ) ;
list.add( "3" ) ;
list.add( "4" ) ;
list.add( "4" ) ;
list.add( "2" ) ;
list.add( "1" ) ;
list.add( "1" ) ;
Observable.from( list )
.distinctUntilChanged()
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println( "distinctUntilChanged--" + s );
}
}) ;
运行结果
从结果可以看出,连续重复的数据已经被过滤掉了
debounce() 操作符一段时间内没有变化,就会发送一个数据
使用场景:百度关键词联想提示。在输入的过程中是不会从服务器拉数据的。当输入结束后,在400毫秒没有输入就会去获取数据。
避免了,多次请求给服务器带来的压力.
doOnSubscribe()
使用场景: 可以在事件发出之前做一些初始化的工作,比如弹出进度条等等
注意:
1. doOnSubscribe()默认运行在事件产生的线程里面,然而事件产生的线程一般都会运行在 io
线程里。那么这个时候做一些,更新UI的操作,是线程不安全的。
所以如果事件产生的线程是io线程,但是我们又要在doOnSubscribe()
更新UI ,这时候就需要线程切换。
1. 如果在 doOnSubscribe() 之后有 subscribeOn() 的话,它将执行在离它最近的 subscribeOn() 所指定的线程。
1. subscribeOn() :事件产生的线程 ; observeOn() : 事件消费的线程
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);
range 操作符
首先看range 方法的源码
public static Observable<Integer> range(int start, int count) {
if (count < 0) {
throw new IllegalArgumentException("Count can not be negative");
}
if (count == 0) {
return Observable.empty();
}
if (start > Integer.MAX_VALUE - count + 1) {
throw new IllegalArgumentException("start + count can not exceed Integer.MAX_VALUE");
}
if(count == 1) {
return Observable.just(start);
}
return Observable.create(new OnSubscribeRange(start, start + (count - 1)));
}
//可以通过第三个参数控制range执行的线程
public static Observable<Integer> range(int start, int count, Scheduler scheduler) {
return range(start, count).subscribeOn(scheduler);
}
Range操作符发射一个范围内的有序整数序列,你可以指定范围的起始和长度。
RxJava将这个操作符实现为range函数,它接受两个参数,一个是范围的起始值,一个是范围的数据的数目。如果你将第二个参数设为0,将导致Observable不发射任何数据(如果设置为负数,会抛异常)。
range默认不在任何特定的调度器上执行。有一个变体可以通过可选参数指定Scheduler。
例子
Observable.range( 10 , 3 )
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Log.v( "rx_range " , "" + integer ) ;
}
}) ;
结果
/rx_range: 10
/rx_range: 11
/rx_range: 12
defer 操作符
例子
public class DeferActivity extends AppCompatActivity {
String i = "10" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_defer);
i = "11 " ;
Observable<String> defer = Observable.defer(new Func0<Observable<String>>() {
@Override
public Observable<String> call() {
return Observable.just( i ) ;
}
}) ;
Observable test = Observable.just( i ) ;
i = "12" ;
defer.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.v( "rx_defer " , "" + s ) ;
}
}) ;
test.subscribe(new Action1() {
@Override
public void call(Object o) {
Log.v( "rx_just " , "" + o ) ;
}
}) ;
}
}
结果
/rx_defer: 12
/rx_just: 11
- 可以看到,just操作符是在创建Observable就进行了赋值操作,而defer是在订阅者订阅时才创建Observable,此时才进行真正的赋值操作。
- Defer操作符会一直等待直到有观察者订阅它,然后它使用Observable工厂方法生成一个Observable。它对每个观察者都这样做,因此尽管每个订阅者都以为自己订阅的是同一个Observable,事实上每个订阅者获取的是它们自己的单独的数据序列。
- 在某些情况下,等待直到最后一分钟(就是直到订阅发生时)才生成Observable可以确保Observable包含最新的数据。
生命周期控制和内存优化
RxJava使我们很方便的使用链式编程,代码看起来既简洁又优雅。但是RxJava使用起来也是有副作用的,使用越来越多的订阅,内存开销也会变得很大,稍不留神就会出现内存溢出的情况。
取消订阅 subscription.unsubscribe() ;
public class MainActivity extends AppCompatActivity {
Subscription subscription ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
subscription = Observable.just( "123").subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println( "tt--" + s );
}
}) ;
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消订阅
if ( subscription != null ){
subscription.unsubscribe();
}
}
}
线程调度
Scheduler调度器,相当于线程控制器,介绍见上文【线程控制 —— Scheduler (一)】【线程控制:Scheduler (二)】
上面介绍了两种控制Rxjava生命周期的方式,第一种:取消订阅 ;第二种:线程切换。这两种方式都能有效的解决android内存的使用问题,但是在实际的项目中会出现很多订阅关系,那么取消订阅的代码也就越来越多。造成了项目很难维护。所以我们必须寻找其他可靠简单可行的方式,也就是下面要介绍的。
rxlifecycle 框架的使用
- github地址: https://github.com/trello/RxLifecycle
- 在android studio 里面添加引用
compile ‘com.trello:rxlifecycle-components:0.6.1’ - 让你的activity继承RxActivity,RxAppCompatActivity,RxFragmentActivity
让你的fragment继承RxFragment,RxDialogFragment;
下面的代码就以RxAppCompatActivity举例
bindToLifecycle 方法
在子类使用Observable中的compose操作符,调用,完成Observable发布的事件和当前的组件绑定,实现生命周期同步。从而实现当前组件生命周期结束时,自动取消对Observable订阅。
public class MainActivity extends RxAppCompatActivity {
TextView textView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
//循环发送数字
Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribeOn( Schedulers.io())
.compose(this.<Long>bindToLifecycle()) //这个订阅关系跟Activity绑定,Observable 和activity生命周期同步
.observeOn( AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println("lifecycle--" + aLong);
textView.setText( "" + aLong );
}
});
}
}
上面的代码是Observable循环的发送数字,并且在textview中显示出来
1、没加 compose(this.bindToLifecycle()) 当Activiry结束掉以后,Observable还是会不断的发送数字,订阅关系没有解除
2、添加compose(this.bindToLifecycle()) 当Activity结束掉以后,Observable停止发送数据,订阅关系解除。
- 从上面的例子可以看出bindToLifecycle() 方法可以使Observable发布的事件和当前的Activity绑定,实现生命周期同步。也就是Activity的 onDestroy() 方法被调用后,Observable的订阅关系才解除。
bindUntilEvent( ActivityEvent event)
指定在Activity其他的生命状态和订阅关系保持同步
- ActivityEvent.CREATE: 在Activity的onCreate()方法执行后,解除绑定。
- ActivityEvent.START:在Activity的onStart()方法执行后,解除绑定。
- ActivityEvent.RESUME:在Activity的onResume()方法执行后,解除绑定。
- ActivityEvent.PAUSE: 在Activity的onPause()方法执行后,解除绑定。
- ActivityEvent.STOP:在Activity的onStop()方法执行后,解除绑定。
- ActivityEvent.DESTROY:在Activity的onDestroy()方法执行后,解除绑定。
//循环发送数字
Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribeOn( Schedulers.io())
.compose(this.<Long>bindUntilEvent(ActivityEvent.STOP )) //当Activity执行Onstop()方法是解除订阅关系
.observeOn( AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println("lifecycle-stop-" + aLong);
textView.setText( "" + aLong );
}
});
经过测试发现,当Activity执行了onStop()方法后,订阅关系已经解除了。
FragmentEvent
这个类是专门处理订阅事件与Fragment生命周期同步的大杀器
public enum FragmentEvent {
ATTACH,
CREATE,
CREATE_VIEW,
START,
RESUME,
PAUSE,
STOP,
DESTROY_VIEW,
DESTROY,
DETACH
}
可以看出FragmentEvent 和 ActivityEvent 类似,都是枚举类,用法是一样的。这里就不举例了!
RxBinding
前言:RxBinding 是 Jake Wharton 的一个开源库,它提供了一套在 Android
平台上的基于 RxJava的 Binding API。所谓 Binding,就是类似设置 OnClickListener
、设置 TextWatcher 这样的注册绑定对象的 API。
git地址
https://github.com/JakeWharton/RxBinding
androidStudio 使用
一般的包下面就用
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
v4’support-v4’ library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0'
‘appcompat-v7’ library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.4.0'
‘design’ library bindings:
compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0'
代码示例
Button 防抖处理
button = (Button) findViewById( R.id.bt ) ;
RxView.clicks( button )
.throttleFirst( 2 , TimeUnit.SECONDS ) //两秒钟之内只取一个点击事件,防抖操作
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(MainActivity.this, "点击了", Toast.LENGTH_SHORT).show();
}
}) ;
按钮的长按时间监听
button = (Button) findViewById( R.id.bt ) ;
//监听长按时间
RxView.longClicks( button)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(MainActivity.this, "long click !!", Toast.LENGTH_SHORT).show();
}
}) ;
listView 的点击事件、长按事件处理
listView = (ListView) findViewById( R.id.listview );
List<String> list = new ArrayList<>() ;
for ( int i = 0 ; i < 40 ; i++ ){
list.add( "sss" + i ) ;
}
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1 );
adapter.addAll( list );
listView.setAdapter( adapter );
//item click event
RxAdapterView.itemClicks( listView )
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Toast.makeText(ListActivity.this, "item click " + integer , Toast.LENGTH_SHORT).show();
}
}) ;
//item long click
RxAdapterView.itemLongClicks( listView)
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Toast.makeText(ListActivity.this, "item long click " + integer , Toast.LENGTH_SHORT).show();
}
}) ;
用户登录界面,勾选同意隐私协议,登录按钮就变高亮
button = (Button) findViewById( R.id.login_bt );
checkBox = (CheckBox) findViewById( R.id.checkbox );
//checkedChanges
RxCompoundButton.checkedChanges( checkBox )
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean aBoolean) {
button.setEnabled( aBoolean );
button.setBackgroundResource( aBoolean ? R.color.button_yes : R.color.button_no );
}
}) ;
效果图
http://o7rvuansr.bkt.clouddn.com/rxbindingGIF.gif
搜索的时候,关键词联想功能 。debounce()在一定的时间内没有操作就会发送事件。
editText = (EditText) findViewById( R.id.editText );
listView = (ListView) findViewById( R.id.listview );
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1 );
listView.setAdapter( adapter );
RxTextView.textChanges( editText )
.debounce( 600 , TimeUnit.MILLISECONDS )
.map(new Func1<CharSequence, String>() {
@Override
public String call(CharSequence charSequence) {
//get the keyword
String key = charSequence.toString() ;
return key ;
}
})
.observeOn( Schedulers.io() )
.map(new Func1<String, List<String>>() {
@Override
public List<String> call(String keyWord ) {
//get list
List<String> dataList = new ArrayList<String>() ;
if ( ! TextUtils.isEmpty( keyWord )){
for ( String s : getData() ) {
if (s != null) {
if (s.contains(keyWord)) {
dataList.add(s);
}
}
}
}
return dataList ;
}
})
.observeOn( AndroidSchedulers.mainThread() )
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
adapter.clear();
adapter.addAll( strings );
adapter.notifyDataSetChanged();
}
}) ;
运行效果
http://o7rvuansr.bkt.clouddn.com/rxbinding_edGIF.gif
总结:
- RxBinding就是把 发布–订阅 的模式用在了android控件的点击,文本变化上。通过RxBinding 把点击监听转换成 Observable 之后,就有了对它进行扩展的可能。
- RxBinding和rxlifecycle 结合起来使用,可以控制控件监听的生命周期。
线程调度实例
Rxjava默认运行的线程
例1
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Logger.v( "rx_call" , Thread.currentThread().getName() );
subscriber.onNext( "dd");
subscriber.onCompleted();
}
})
.map(new Func1<String, String >() {
@Override
public String call(String s) {
Logger.v( "rx_map" , Thread.currentThread().getName() );
return s + "88";
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Logger.v( "rx_subscribe" , Thread.currentThread().getName() );
}
}) ;
结果
/rx_call: main -- 主线程
/rx_map: main -- 主线程
/rx_subscribe: main -- 主线程
例2
new Thread(new Runnable() {
@Override
public void run() {
Logger.v( "rx_newThread" , Thread.currentThread().getName() );
rx();
}
}).start();
void rx(){
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Logger.v( "rx_call" , Thread.currentThread().getName() );
subscriber.onNext( "dd");
subscriber.onCompleted();
}
})
.map(new Func1<String, String >() {
@Override
public String call(String s) {
Logger.v( "rx_map" , Thread.currentThread().getName() );
return s + "88";
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Logger.v( "rx_subscribe" , Thread.currentThread().getName() );
}
}) ;
}
* 结果*
/rx_newThread: Thread-564 -- 子线程
/rx_call: Thread-564 -- 子线程
/rx_map: Thread-564 -- 子线程
/rx_subscribe: Thread-564 -- 子线程
结论
通过例1和例2,说明,Rxjava**默认运行在当前线程**中。如果当前线程是子线程,则rxjava运行在子线程;同样,当前线程是主线程,则rxjava运行在主线程
subscribeOn、observeOn对线程影响
例3
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Logger.v( "rx_call" , Thread.currentThread().getName() );
subscriber.onNext( "dd");
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<String, String >() {
@Override
public String call(String s) {
Logger.v( "rx_map" , Thread.currentThread().getName() );
return s + "88";
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Logger.v( "rx_subscribe" , Thread.currentThread().getName() );
}
}) ;
结果
/rx_call: RxCachedThreadScheduler-1 --io线程
/rx_map: main --主线程
/rx_subscribe: main --主线程
例4
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Logger.v( "rx_call" , Thread.currentThread().getName() );
subscriber.onNext( "dd");
subscriber.onCompleted();
}
})
.map(new Func1<String, String >() {
@Override
public String call(String s) {
Logger.v( "rx_map" , Thread.currentThread().getName() );
return s + "88";
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Logger.v( "rx_subscribe" , Thread.currentThread().getName() );
}
}) ;
结果
/rx_call: RxCachedThreadScheduler-1 --io线程
/rx_map: RxCachedThreadScheduler-1 --io线程
/rx_subscribe: main --主线程
结论
- 通过例3、例4 可以看出 .subscribeOn(Schedulers.io())和 .observeOn(AndroidSchedulers.mainThread())写的位置不一样,造成的结果也不一样。
从例4中可以看出 map()操作符默认运行在事件产生的线程之中。事件消费只是在 subscribe() 里面。- 对于 create() , just() , from() 等 — 事件产生
map() , flapMap() , scan() , filter() 等 – 事件加工
subscribe() – 事件消费- 事件产生:默认运行在当前线程,可以由 subscribeOn() 自定义线程
事件加工:默认跟事件产生的线程保持一致, 可以由 observeOn() 自定义线程
事件消费:默认运行在当前线程,可以有observeOn() 自定义
多次切换线程
例5
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Logger.v( "rx_call" , Thread.currentThread().getName() );
subscriber.onNext( "dd");
subscriber.onCompleted();
}
})
.observeOn( Schedulers.newThread() ) //新线程
.map(new Func1<String, String >() {
@Override
public String call(String s) {
Logger.v( "rx_map" , Thread.currentThread().getName() );
return s + "88";
}
})
.observeOn( Schedulers.io() ) //io线程
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
Logger.v( "rx_filter" , Thread.currentThread().getName() );
return s != null ;
}
})
.subscribeOn(Schedulers.io()) //定义事件产生线程:io线程
.observeOn(AndroidSchedulers.mainThread()) //事件消费线程:主线程
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Logger.v( "rx_subscribe" , Thread.currentThread().getName() );
}
}) ;
结果
/rx_call: RxCachedThreadScheduler-1 -- io 线程
/rx_map: RxNewThreadScheduler-1 -- new出来的线程
/rx_filter: RxCachedThreadScheduler-2 -- io线程
/rx_subscribe: main -- 主线程
只规定了事件产生的线程
例6:
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Log.v( "rx--create " , Thread.currentThread().getName() ) ;
subscriber.onNext( "dd" ) ;
}
})
.subscribeOn(Schedulers.io())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.v( "rx--subscribe " , Thread.currentThread().getName() ) ;
}
}) ;
结果
/rx--create: RxCachedThreadScheduler-4 // io 线程
/rx--subscribe: RxCachedThreadScheduler-4 // io 线程
只规定事件消费线程
例:7:
Observable
.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
Log.v( "rx--create " , Thread.currentThread().getName() ) ;
subscriber.onNext( "dd" ) ;
}
})
.observeOn( Schedulers.newThread() )
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.v( "rx--subscribe " , Thread.currentThread().getName() ) ;
}
}) ;
结果
/rx--create: main -- 主线程
/rx--subscribe: RxNewThreadScheduler-1 -- new 出来的子线程
结论
从例6可以看出,如果只规定了事件产生的线程,那么事件消费线程将跟随事件产生线程。
从例7可以看出,如果只规定了事件消费的线程,那么事件产生的线程和当前线程保持一致。
线程调度封装
例8:
在Android 常常有这样的场景,后台处理处理数据,前台展示数据。
一般的用法:
Observable
.just( "123" )
.subscribeOn( Schedulers.io())
.observeOn( AndroidSchedulers.mainThread() )
.subscribe(new Action1() {
@Override
public void call(Object o) {
}
}) ;
简单的封装
public Observable apply( Observable observable ){
return observable.subscribeOn( Schedulers.io() )
.observeOn( AndroidSchedulers.mainThread() ) ;
}
使用
apply( Observable.just( "123" ) )
.subscribe(new Action1() {
@Override
public void call(Object o) {
}
}) ;
弊端:虽然上面的这种封装可以做到线程调度的目的,但是它破坏了链式编程的结构,是编程风格变得不优雅。
改进:Transformers的使用(就是转化器的意思,把一种类型的Observable转换成另一种类型的Observable )
改进后的封装
//Transformers转化器:把一种类型的Observable转换成另一种类型的Observable
Observable.Transformer schedulersTransformer = new Observable.Transformer() {
@Override public Object call(Object observable) {
return ((Observable) observable).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
}
};
使用
Observable
.just( "123" )
.compose( schedulersTransformer )
.subscribe(new Action1() {
@Override
public void call(Object o) {
}
}) ;
弊端:虽然保持了链式编程结构的完整,但是每次调用 .compose(schedulersTransformer ) 都是 new了一个对象的。所以我们需要再次封装,尽量保证单例的模式。
最优改进后的封装
public class RxUtil {
private final static Observable.Transformer schedulersTransformer
= new Observable.Transformer() {
@Override public Object call(Object observable) {
return ((Observable) observable).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
}
};
public static <T> Observable.Transformer<T, T> applySchedulers() {
return (Observable.Transformer<T, T>) schedulersTransformer;
}
}
使用
Observable
.just( "123" )
.compose( RxUtil.<String>applySchedulers() )
.subscribe(new Action1() {
@Override
public void call(Object o) {
}
}) ;
相关推荐
另外推荐几篇比较好的文章,有助于理解Rxjava
安卓客户端是如何使用 RxJava 的
通过 RxJava 实现一个 Event Bus –RxBus
玩透RxJava(一)基础知识
RxJava 教程第二部分:事件流基础之过滤数据
Meizhi Android之RxJava &Retrofit最佳实践
相关代码: http://git.oschina.net/zyj1609/RxAndroid_RxJava
引用:
给 Android 开发者的 RxJava 详解
RxJava 和 RxAndroid 二(操作符的使用)
RxJava 和 RxAndroid 三(生命周期控制和内存优化)
RxJava 和 RxAndroid 四(RxBinding的使用)
RxJava 和 RxAndroid五(线程调度)