前言
RxJava2.0从入门到放弃(一)中简单介绍了我对RxJava的理解以及RxJava最基本的一个写法。这一部分继续讲讲RxJava最重要的一个环节关于线程得调度。(本文案例用kotlin来做案例。)
至于为什么说是最重要的?RxJava在github是这么定义自己的
RxJava is a Java VM implementation of [Reactive Extensions](http://reactivex.io/): a library for composing asynchronous and event-based programs by using observable sequences.
也就是说RxJava是一个专注于用来解决异步以及事件驱动的库。
讲解之前我们先抛出一个问题吧:
先从IO线程独读一个文件夹,
再把文件夹里面的png图片筛选出来,
然后在主线程中把这些图片加载在UI上。
面对这样一个需求该怎么处理。用Thread应该差不多这样实现
final File[] files = file.listFiles();
new Thread(new Runnable() {
@Override
public void run() {
for(File f:files) {
if(f.getName().endsWith(".png")){
final Bitmap bitmap = transFileToBitmap(f);
runOnUiThread(new Runnable() {
@Override
public void run() {
updateUI(bitmap);
}
});
}
}
}
}).start();
后面再来用RxJava来实现一下这个需求。
正文
RxJava整体的线程调度涉及到三个关键点分别是subscribeOn
observeOn
Scheduler
。
RxJava在不指定线程的情况下,RxJava保持者线程不变的原则。也就是说『上游』在哪个线程上创建事件,『下游』就是在哪个线程上处理事件,『上游』和『下游』线程保持一致。
用代码来验证下:
Observable.create { e ->
for (i in 0..5) {
Log.e(TAG, "Observable thread ${Thread.currentThread().name}")
Log.e(TAG, "observable $i")
e.onNext(i)
}
}
.subscribe { int ->
Log.e(TAG, "onNext $int")
Log.e(TAG, "subscribe thread ${Thread.currentThread().name}")
}
输出结果是这样:
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 0
08-23 17:55:33.635 19473 19473 E RxTag : onNext 0
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 1
08-23 17:55:33.635 19473 19473 E RxTag : onNext 1
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 2
08-23 17:55:33.635 19473 19473 E RxTag : onNext 2
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 3
08-23 17:55:33.635 19473 19473 E RxTag : onNext 3
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 4
08-23 17:55:33.635 19473 19473 E RxTag : onNext 4
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
08-23 17:55:33.635 19473 19473 E RxTag : Observable thread main
08-23 17:55:33.635 19473 19473 E RxTag : observable 5
08-23 17:55:33.635 19473 19473 E RxTag : onNext 5
08-23 17:55:33.635 19473 19473 E RxTag : subscribe thread main
可以看到所有的运行都是在main线程运行的,可以验证:
RxJava在不指定线程的情况下,『上游』和『下游』线程保持一致。
如果指定线程的话该怎么做?
在RxJava中可以分别通过 subscribeOn()
和observerOn()
这两个方法来指定『上游』事件产生的线程以及『下游』事件响应的线程。
具体怎么做我们来看代码:
Observable.create { e ->
for (i in 0..2) {
Log.e(TAG, "Observable thread ${Thread.currentThread().name}")
Log.e(TAG, "observable $i")
e.onNext(i)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { int ->
Log.e(TAG, "onNext $int")
Log.e(TAG, "subscribe thread ${Thread.currentThread().name}")
}
输出结果是:
08-24 11:02:28.614 19473 21708 E RxTag : Observable thread RxCachedThreadScheduler-1
08-24 11:02:28.614 19473 21708 E RxTag : observable 0
08-24 11:02:28.614 19473 21708 E RxTag : Observable thread RxCachedThreadScheduler-1
08-24 11:02:28.615 19473 21708 E RxTag : observable 1
08-24 11:02:28.615 19473 21708 E RxTag : Observable thread RxCachedThreadScheduler-1
08-24 11:02:28.615 19473 21708 E RxTag : observable 2
08-24 11:02:28.982 19473 19473 E RxTag : onNext 0
08-24 11:02:28.982 19473 19473 E RxTag : subscribe thread main
08-24 11:02:28.982 19473 19473 E RxTag : onNext 1
08-24 11:02:28.983 19473 19473 E RxTag : subscribe thread main
08-24 11:02:28.983 19473 19473 E RxTag : onNext 2
08-24 11:02:28.983 19473 19473 E RxTag : subscribe thread main
『上游』事件产生在
RxCachedThreadScheduler-1
这个线程,『下游』事件响应的onNext()在main
线程。
那么我们关注下Scheduler的几个线程名称:
- Schedulers.trampoline() : 相当于不指定线程。直接在之前的线程运行,依赖于调用操作的线程。
- Schedulers.io():(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率;
- Schedulers.newThread(): 总是启用新线程,并在新线程中执行操作;
- Schedulers.single(): 启用一个线程池大小为1的线程池,相当于(newScheduledThreadPool(1)),重复利用这个线程;
- Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
在RxAndroid中就会有这么一个线程:
- AndroidSchedulers.mainThread():运行在Android主线程中。main UI线程。
那么在Android中最简单的异步或者请求网络就这么写了:
Observable.create { e ->
//请求网络,返回一个String
val str:String = api.getString()
e.onNext(str)
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
str ->
//获得String,更新到UI
updateUI(str)
},{
error->
//连接错误,提示错误信息
netWorkError(error.message)
})
回顾文章最开始提出的问题我们就可以这么的用 RxJava实现出来:
Observable.fromArray(f.listFiles())
.filter(new Predicate() {
@Override
public boolean test(File file) throws Exception {
return file.getName().endsWith(".png");
}
})
.map(new Function() {
@Override
public Bitmap apply(File file) throws Exception {
return getBitMapFromFile(file);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(Bitmap bitmap) throws Exception {
updateUI(bitmap);
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
showError(throwable.getMessage());
}
});
}
但是有两点需要注意:
- subscribeOn 讲『上游』事件的发射切换到 Scheduler 所定义的线程, 如果多次调用 subscribeOn(),那么只有第一个 subscribeOn 操作有效 ;
- observeOn 指定 observeOn 后续操作所在线程。也就是说 可以多次调用observeOn 可以多次切换接下来『下游』事件处理的线程 ;
举个栗子吧:
Observable.create { emitter ->
for (i in 0..2) {
Log.e(TAG, "Observable thread ${Thread.currentThread().name}")
Log.e(TAG, "observable $i")
emitter.onNext(i)
}
}
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.computation())
.doOnNext(consumer())
.observeOn(Schedulers.io())
.doOnNext(consumer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(consumer())
}
private fun consumer(): Consumer {
return Consumer { i ->
Log.e(TAG, "onNext thread ${Thread.currentThread().name}")
Log.e(TAG, "onNext $i")
}
}
看一看输出结果就证明之前的注意点:
08-24 14:54:41.670 26674 27052 E RxTag : Observable thread RxCachedThreadScheduler-6
08-24 14:54:41.670 26674 27052 E RxTag : observable 0
08-24 14:54:41.670 26674 27052 E RxTag : Observable thread RxCachedThreadScheduler-6
08-24 14:54:41.670 26674 27052 E RxTag : observable 1
08-24 14:54:41.670 26674 27052 E RxTag : Observable thread RxCachedThreadScheduler-6
08-24 14:54:41.670 26674 27052 E RxTag : observable 2
08-24 14:54:41.672 26674 26880 E RxTag : onNext thread RxComputationThreadPool-2
08-24 14:54:41.688 26674 26880 E RxTag : onNext 0
08-24 14:54:41.688 26674 26880 E RxTag : onNext thread RxComputationThreadPool-2
08-24 14:54:41.688 26674 26880 E RxTag : onNext 1
08-24 14:54:41.688 26674 26880 E RxTag : onNext thread RxComputationThreadPool-2
08-24 14:54:41.688 26674 26880 E RxTag : onNext 2
08-24 14:54:41.691 26674 27054 E RxTag : onNext thread RxCachedThreadScheduler-7
08-24 14:54:41.691 26674 27054 E RxTag : onNext 0
08-24 14:54:41.697 26674 27054 E RxTag : onNext thread RxCachedThreadScheduler-7
08-24 14:54:41.697 26674 27054 E RxTag : onNext 1
08-24 14:54:41.698 26674 27054 E RxTag : onNext thread RxCachedThreadScheduler-7
08-24 14:54:41.698 26674 27054 E RxTag : onNext 2
08-24 14:54:42.058 26674 26674 E RxTag : onNext thread main
08-24 14:54:42.058 26674 26674 E RxTag : onNext 0
08-24 14:54:42.058 26674 26674 E RxTag : onNext thread main
08-24 14:54:42.059 26674 26674 E RxTag : onNext 1
08-24 14:54:42.059 26674 26674 E RxTag : onNext thread main
08-24 14:54:42.059 26674 26674 E RxTag : onNext 2
我们调用了两次 subscribeOn()分别是 io()和newThread(),但是输出结果就只有在RxCachedThreadScheduler -6
线程中。但是每次调用doOnNext()
都切换了一个线程,也就是说可以随时随地切换事件的处理线程。
总结
线程的调度就到这结束了,把握好subscribeOn
observableOn
以及scheduler
的切换,就能随心所欲的进行切换线程切换啦。