暂时没时间把rxJava&rxAndroid的进阶内容全部整理完,先整理一部分
附上博客链接:
http://blog.csdn.net/wds1181977/article/details/51554170
注意,下文说的Subscriber 就是基础篇里面讲的Observer,因为在rxJava的源码里面,无论你使用Observer还是Subscriber 最后都会被转换成Subscriber 来执行~~
一、Scheduler线程控制
前面基础篇我们看到的 subscribeOn() 和 observeOn() 就是Scheduler线程控制的一部分。
注意:
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。
rxJava有如下几个Scheduler线程参数:
- Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
- Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
- Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
- Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
- AndroidSchedulers.mainThread():它指定的操作将在 Android 主线程运行,针对View的操作都要在这个上进行
有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。 总而言之,就是:
**subscribeOn()
: 指定subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。 **
**observeOn()
: 指定 Subscriber (等同前面的Observer)所运行在的线程。或者叫做事件消费的线程。 **
由图片 id 取得图片并显示的例子:
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe() {
@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() {
@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 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。
二、变换(上):map()
这一部分的关键词是
“变换”
那么,何为“变换”呢?我们继续往下看
在基础篇里面我们提到了Map,下面有一段代码
Observable.just("images/logo.png") // 输入类型 String
.map(new Func1() {
@Override
public Bitmap call(String filePath) { // 参数类型 String
return getBitmapFromPath(filePath); // 返回类型 Bitmap
}
})
.subscribe(new Action1() {
@Override
public void call(Bitmap bitmap) { // 参数类型 Bitmap
showBitmap(bitmap);
}
});
这里我们可以看到,Observable将传入的String类型转变成了Bitmap类型,这个转变关系就是我们要讲的“变换”
如上所示,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由String 转为了 Bitmap。这种直接变换对象并返回的,是最常见的也最容易理解的变换。
除此之外, RxJava 还可以针对整个事件队列进行变换操作,这使得 RxJava 变得非常灵活。我列举几个常用的变换:
-
map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava 最常用的变换。 map() 的示意图:
三、变换(下):flatMap()
flatMap(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
Student[] students = ...;
Subscriber subscriber = new Subscriber() {
@Override
public void onNext(String name) {
Log.d(tag, name);
}
...
};
Observable.from(students)
.map(new Func1() {
@Override
public String call(Student student) {
return student.getName();
}
})
.subscribe(subscriber);
很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:
Student[] students = ...;
Subscriber subscriber = new Subscriber() {
@Override
public void onNext(Student student) {
List 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 subscriber = new Subscriber() {
@Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1>() {
@Override
public Observable call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。
首先我们先要理解这样一个前提:这里一个Students数组当做事件对象创建了一个Oberable对象,然后绑定了一个Subscriber,Oberable对象会自动将这个Students数组拆解,然后把一个一个Student对象传给Subscriber,然后Subscriber不断响应传入的Student对象,利用循环加载单个Student的好多个课程,这也是之前使用map()的原理。
而现在我们要求传给Subscriber的必须是多个学生的多个课程。那么利用一种类似递归的思想去理解这个问题。flatMap() 在这里的原理是这样的:
- 使用传入的Students数组事件对象创建一个 Observable 对象
- 并不发送这个 Observable, 而是将它激活,于是它开始发送事件,也就是这里它发送Observable
,注意这个Course也是一个数组(或者任一集合框架类型)哦 - 看这里的代码
public Observable call(Student student) {
return Observable.from(student.getCourses());
}
这里return的Observable.from(student.getCourses())
并没有直接绑定Subscriber,而是又经过了一个二级传递,将原来student.getCourses()
中获得的一个ListObservable
- 然后这个返回类型中的
Observable
将这些course统一交给 Subscriber 的回调方法
总结:每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat
flatMap() 示意图:
扩展:由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求。示例代码(Retrofit + RxJava):
networkClient.token() // 返回 Observable,在订阅时请求 token,并在响应后发送 token
.flatMap(new Func1>() {
@Override
public Observable call(String token) {
// 返回 Observable,在订阅时请求消息列表,并在响应后发送请求到的消息列表
return networkClient.messages();
}
})
.subscribe(new Action1() {
@Override
public void call(Messages messages) {
// 处理显示消息列表
showMessages(messages);
}
});
传统的嵌套请求需要使用嵌套的 Callback 来实现。而通过 flatMap() ,可以把嵌套的请求写在一条链中,从而保持程序逻辑的清晰。
throttleFirst(): 在每次事件触发后的一定时间间隔内丢弃新的事件。常用作去抖动过滤,例如按钮的点击监听器:RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释 .throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms .subscribe(subscriber);妈妈再也不怕我的用户手抖点开两个重复的界面啦。
此外, RxJava 还提供很多便捷的方法来实现事件序列的变换,这里就不一一举例了。
暂时先写到这里。