在介绍线程切换原理之前,介绍一下在 Rxjava2 中如何进行线程切换
祭出官网图
有两个操作符来决定如何切换线程:
subscribeOn指定上游subscribe()所发生的线程observeOn指定Subscriber所运行在的线程
说人话就是,subscribeOn 决定从事件源发过来的线程,而 observeOn 决定每调用一次它之后的订阅发生在哪个线程里。
Single.subscribeOn() 事件流订阅
Single
.just(1L)
.subscribeOn(Schedulers.io())
点进去看 subscribeOn 源码,跳过钩子调用后,ObservableSubscribeOn 为其实现类
@Override
public void subscribeActual(final Observer<? super T> observer) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer);
observer.onSubscribe(parent);
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
内部实现为创建一个 SubscribeOnObserver 来传给下游订阅者。
同时,调用调度器的 scheduleDirect 来切线程,SubscribeTask 实现了 Runnable
static final class SubscribeOnObserver<T>
extends AtomicReference<Disposable>
implements SingleObserver<T>, Disposable, Runnable {
@Override
public void run() {
source.subscribe(this);
}
}
在 run 方法中执行订阅过程,这样以来整个对于上游的订阅发生在调度器所在线程。
如图所示:
操作符线程订阅
再增加一个操作符,查看执行所在线程情况。
我们用白线来指代调用Rxjava所处的原有线程,
用紫色代表使用 SubscribeOn 指定订阅的线程
可以看出经过 SubscribeOn 指定订阅所在的线程之后,从上流过来的数据至下流接受处理数据并返回订阅器,都会执行在我们执行的线程当中。
重复 SubscribeOn 线程切换
如果我们有多个 SubscribeOn ,那么线程会如何切换呢 ?
用紫色箭头代表代码中第一个指定的线程,绿色箭头表示后续指定的线程,白色箭头代表调用 Rxjava 原有所处线程。
虽然最后指定的 SubscribeOn 距离真实的 Observer 比较近,由于先进行了线程切换,等到遇到离上游最近的 SingleSubscribeOn 时,它会将线程再重新切换到它所指定的 Schedule。
从源码角度也可以看出,无论我们指定多少次 subscribeOn,最终有效的是最先指定的那个 subscribeOn。
Dispose 原理
public final class SingleSubscribeOn<T> extends Single<T> {
@Override
protected void subscribeActual(final SingleObserver<? super T> observer) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer, source);
Disposable f = scheduler.scheduleDirect(parent);
parent.task.replace(f);
}
}
@Override
public void onSubscribe(Disposable d) {
DisposableHelper.setOnce(this, d);
}
@Override
public void dispose() {
DisposableHelper.dispose(this);
task.dispose();
}
在调用 dispose 时,SingleSubscribeOn 会调用两块
- 上游 onSubscribe 传入的 Disposable
- 切换线程的 Disposable
目的,通知上游不要继续发数据,本身切换线程的操作也停止。
Single.observeOn 线程切换
Rxjava 中还有一个常用来线程切换的操作符
ObserveOn 指定线程后,在此之后做订阅的 observer 的 onSuccess、onError 都会被切换到指定线程。
我们如下使用 ObserveOn
Single.just(1)
.observeOn(AndroidSchedulers.mainThread())
查看源码,最终实现是在 SingleObserveOn 类中,查看其 subscribeActual
@Override
protected void subscribeActual(final SingleObserver<? super T> observer) {
source.subscribe(new ObserveOnSingleObserver<T>(observer, scheduler));
}
实际实现在 ObserveOnSingleObserver 中进行包装。
static final class ObserveOnSingleObserver<T> extends AtomicReference<Disposable>
implements SingleObserver<T>, Disposable, Runnable {
@Override
public void onSubscribe(Disposable d) {
if (DisposableHelper.setOnce(this, d)) {
downstream.onSubscribe(this);
}
}
@Override
public void onSuccess(T value) {
this.value = value;
Disposable d = scheduler.scheduleDirect(this);
DisposableHelper.replace(this, d);
}
@Override
public void onError(Throwable e) {
this.error = e;
Disposable d = scheduler.scheduleDirect(this);
DisposableHelper.replace(this, d);
}
@Override
public void run() {
Throwable ex = error;
if (ex != null) {
downstream.onError(ex);
} else {
downstream.onSuccess(value);
}
}
@Override
public void dispose() {
DisposableHelper.dispose(this);
}
}
这个代理 observer 和 subscribeOn 操作符的区别在于,subscribeOn 操作符是在接收到订阅时直接进行切换线程操作。而 ObserveOn 则是在 onSuccess、OnError 中进行线程的切换,其不影响上游的数据处理线程,只影响下游接收订阅结果所处的线程。
我们用白色箭头指代调用 Rxjava 订阅所处线程,红色箭头表示 ObserveOn 中指定要切换的线程。
从图中也可以明显看出,ObserveOn 只影响后续订阅结果的线程,不影响上游处理数据的线程
组合 Map 切换线程
Single.just(1)
.observeOn(AndroidSchedulers.mainThread())
.map(object : Function<Int, Int> {
override fun apply(t: Int): Int {
return t
}
})
其运行实际线程如下图所示
用白色箭头指代调用 Rxjava 订阅所处线程,红色箭头表示 ObserveOn 中指定要切换的线程。
可以看出组合 map 之后,整个 ObserveOn 的下游 Map 订阅、用户订阅结果线程都被切换成了ObserveOn 内指定线程
结合 SubscribeOn 切换线程
Single.just(1)
.observeOn(Schedulers.computation())
.map(object : Function<Int, Int> {
override fun apply(t: Int): Int {
return t
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : SingleObserver<Int?> {
demo 中我们指定 map 运行在 computation 调度器中,用户订阅在安卓主线程。
用白色箭头指代调用 Rxjava 订阅所处线程,紫色箭头表示希望数据源执行的线程,蓝色箭头代表,在 Map 操作符中的监听,红色箭头表示用户定义在 ObserveOn 中指定要切换的线程。
我们可以看出,在 ObserveOn 和 SubscribeOn 进行组合时,具体 SubscribeOn 所处的定义位置,其实并不影响实际上订阅线程,以及其他订阅需要执行在的线程。具体每一个操作符如果需要切线程,只需要在操作符前增加一个 ObserveOn 操作符即可。
Dispose 原理
@Override
public void onSubscribe(Disposable d) {
if (DisposableHelper.setOnce(this, d)) {
downstream.onSubscribe(this);
}
}
@Override
public void onSuccess(T value) {
this.value = value;
Disposable d = scheduler.scheduleDirect(this);
DisposableHelper.replace(this, d);
}
@Override
public void onError(Throwable e) {
this.error = e;
Disposable d = scheduler.scheduleDirect(this);
DisposableHelper.replace(this, d);
}
@Override
public void dispose() {
DisposableHelper.dispose(this);
}
ObserveOn 中的 dispose 分为几种情况
- 上游数据尚未发送到 onSuccess 、OnError,此时取消的是上游
- 上游已经发送到 ObserveOn 中,正在切线程,
DisposableHelper.replace(this, d);,此时取消的就是切线程的操作。
到此位置,有关 SubscribeOn 以及 ObserveOn 如何进行线程切换的原理和流程走向就介绍完了。
做一些总结:
- SubscribeOn 会切换从数据源发送的线程
- SubscribeOn 所处的位置与其他操作符位置无关,具有两个 SubscribeOn 操作符时,以最顶层的 SubscribeOn 操作符为准
- 线程切换中没有 ObserveOn 时,整个数据流的线程都会被切换成 SubscribeOn 指定的线程,在有 ObserveOn 的线程时,数据流会影响操作符之后的传递线程。
在之后的文章中会对 Schedules 的内部实现逻辑进行更细致的介绍。