前言
上篇文章梳理了整个RxJava/RxSwift的定义、订阅、事件产生-->消费的过程与源码解析,本文主要是想通过前文总结的思路聊聊RxJava的subscribeOn与observeOn的源码实现,如果没看过前文的建议先移步阅读。由于前文的涉及了两个语言的Rx版本,导致篇幅较大,本文先着重总结一下RxJava版本的,后续会出一篇RxSwift版本(ps:本文Rx源码基于RxJava 2.1.16)。
流程
基于前文所解析的Observable.create,我们新增subscribeOn与observeOn两个操作符。随码附赠一个流程图,主要是补充前文解析的代码数据流程加上本次我们聊到的subscribeOn与observeOn。
Observable.create<String> { // it == CreateEmitter
Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
it.onNext("rx")
it.onComplete()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { // onNext
Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
Log.d(TAG, it)
}
从前文的代码解析可知,RxJava在订阅期间依赖的是抽象方法subscribeActual的实现。事件下发依赖的则是onNext/onError等方法的实现。故下面的文章也会围绕这两方面来总结subscribeOn与observeOn的实现。
subscribeOn
外层调用subscribeOn方法,新建一个ObservableSubscribeOn对象,以自身作为ObservableSubscribeOn的source。
// Obserable.java
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> subscribeOn(Scheduler scheduler) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
// ObservableSubscribeOn.java
public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
final Scheduler scheduler;
public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
super(source);
this.scheduler = scheduler;
}
...
源码:
- subscribeOn方法:Observable.java 12359行
- ObservableSubscribeOn.java
subscribeActual
在调用subscribeActual时,
- 创建SubscribeOnObserver对象,用于装饰我们传入的s(上一级的观察者Observer)。
- 创建一个SubscribeTask对象,将SubscribeOnObserver作为其成员变量,下述代码说明了SubscribeTask是一个Runnable对象。
- 这里有一个利用scheduler设置SubscribeTask对象(Runnable对象)的操作,本文暂不涉及scheduler线程调度器的代码解析,这里可以简单理解为就是调起了线程调度。
- 再来看看SubscribeTask对象(Runnable对象)的run方法,即在线程调度到此Runnable时的逻辑是source.subscribe(parent);source即为ObservableSubscribeOn创建时传入的source,parent即SubscribeOnObserver,也就是订阅在切换线程后继续往上传递了。
// ObservableSubscribeOn.java
@Override
public void subscribeActual(final Observer<? super T> s) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s);
s.onSubscribe(parent);
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
// ObservableSubscribeOn.java
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent;
SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
}
@Override
public void run() {
source.subscribe(parent);
}
}
源码:
onNext
参照开头的流程图,事件流产生通过onNext往下传递就是通过SubscribeOnObserver的onNext方法。这里只是单纯调用了起成员变量actual(就是在subscribeActual方法中传入的observer,也就是其下一级的观察者)的onNext方法,并没有进行线程处理。
static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable {
private static final long serialVersionUID = 8094547886072529208L;
final Observer<? super T> actual;
final AtomicReference<Disposable> s;
SubscribeOnObserver(Observer<? super T> actual) {
this.actual = actual;
this.s = new AtomicReference<Disposable>();
}
...
@Override
public void onNext(T t) {
actual.onNext(t);
}
...
源码:
observeOn
外层调用observeOn方法,新建一个ObservableObserveOn对象,以自身作为ObservableObserveOn的source。
// Observable.java
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler) {
return observeOn(scheduler, false, bufferSize());
}
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
ObjectHelper.verifyPositive(bufferSize, "bufferSize");
return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
}
源码:
- observeOn方法:Observable.java 9694、9759行
- ObservableObserveOn.java
值得一提的是,这里会设置一个默认的bufferSize,该参数是用于定义observeOn内缓存队列大小的,这个后面会讲到。bufferSize()引用的是Flowable.bufferSize()。这里会读取一个系统属性rx2.buffer-size,该值默认情况下默认为128,如果想对此调整的话也可在合适时机修改该值。
// Observable.java
public static int bufferSize() {
return Flowable.bufferSize();
}
// Flowable.java
/** The default buffer size. */
static final int BUFFER_SIZE;
static {
BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));
}
subscribeActual
在调用subscribeActual时,
- 创建一个Scheduler.Worker对象,这个是封装关于Rx里面线程调度的逻辑。
- 创建一个包装类ObserveOnObserver对象。
- 调用source(ObservableObserveOn创建时传入的source)的subscribe以ObserveOnObserver对象为参数,将订阅往上传递。
- 注意:这里没有涉及切换线程的逻辑。
// ObservableObserveOn.java
@Override
protected void subscribeActual(Observer<? super T> observer) {
if (scheduler instanceof TrampolineScheduler) {
source.subscribe(observer);
} else {
Scheduler.Worker w = scheduler.createWorker();
source.subscribe(new ObserveOnObserver<T>(observer, w, delayError, bufferSize));
}
}
源码:
onNext
这里先插播一下上文提及的buffersize,其作用是初始化ObserveOnObserver内的队列(SimpleQueue queue)。触发时机是在onSubscribe。该队列是用于后续在onNext的事件触发时用于缓存事件方便线程切换的。
// ObservableObserveOn.java
@Override
public void onSubscribe(Disposable s) {
if (DisposableHelper.validate(this.s, s)) {
this.s = s;
...
queue = new SpscLinkedArrayQueue<T>(bufferSize);
actual.onSubscribe(this);
}
}
参照开头的流程图,事件流产生通过onNext往下传递经过ObserveOnObserver的onNext方法。onNext中,并没有接着往下调用onNext,而是先将数据缓存到上述提及的队列queue,然后调用schedule()方法。
// ObservableObserveOn.java
@Override
public void onNext(T t) {
if (done) {
return;
}
if (sourceMode != QueueDisposable.ASYNC) {
queue.offer(t);
}
schedule();
}
schedule()方法中调用worker.schedule,切换线程执行的逻辑。这里传入的this是因为ObserveOnObserver自身也是一个Runnable对象。所以我们可以通过阅读它的run方法得知切换线程后的运行逻辑。
// ObservableObserveOn.java
static final class ObserveOnObserver<T> extends BasicIntQueueDisposable<T>
implements Observer<T>, Runnable {
...
void schedule() {
if (getAndIncrement() == 0) {
worker.schedule(this);
}
}
run方法,
- run方法中标志位outputFused一般为false,这里我们先看drainNormal方法。
- drainNormal方法中利用死循环将之前缓存到队列(queue)里面的内容逐一调用成员变量actual(就是在subscribeActual方法中传入的observer,也就是其下一级的观察者)的onNext方法。当然,在poll抛出异常时会回调onError处理。注意,这里的逻辑已经在我们最开始设置的scheduler对应的线程运行了。
- 还有一个比较有意思的线程同步设计,ObserveOnObserver本身也是一个AtomicInteger对象。在schedule()方法中有getAndIncrement(),而在drainNormal第一层循环的最后有addAndGet,这个实际上是AtomicInteger本身的value值+1、-1。以此来保证上述的worker.schedule(this);逻辑在并发情况下只被调用一次,保证线程安全。
@Override
public void run() {
if (outputFused) {
drainFused();
} else {
drainNormal();
}
}
...
void drainNormal() {
int missed = 1;
final SimpleQueue<T> q = queue;
final Observer<? super T> a = actual;
for (;;) {
if (checkTerminated(done, q.isEmpty(), a)) {
return;
}
for (;;) {
boolean d = done;
T v;
try {
v = q.poll();
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
s.dispose();
q.clear();
a.onError(ex);
worker.dispose();
return;
}
boolean empty = v == null;
if (checkTerminated(d, empty, a)) {
return;
}
if (empty) {
break;
}
a.onNext(v);
}
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
源码:
总结
至此,subscribeOn与observeOn的源码就解析完了。需要理解的点是:
- subscribeOn的线程调度发生在subscribeActual,即订阅阶段。
- observeOn的线程调度则发生在onNext/onError等事件加工/消费阶段。 接下来我们用上述提到的源码科学地分析来解决经常在Rx中遇到的线程问题。
常见问题
有时候看到一些教程中提到什么“事件产生的线程取决于subscribeOn”,“事件加工的线程默认与事件消费线程相同,受observeOn影响”等总结。这种常见问题就可以通过结合源码来解析了。
Rx默认会在哪个线程运行?
下列代码可以看到,代码的调用在main线程,最后的输出结果为都在main线程运行。
需要理解的是,Rx并不是默认在main线程,而是默认在当前线程。因为唯有subscribeOn与observeOn两个操作符具有线程调度的能力,准确地说是subscribeOn与observeOn中的scheduler拥有线程调度的能力,两个操作符只是在流程上做出控制而已。
所以说,Rx的代码在没有scheduler参与的情况下,没有切换线程,也就在当前线程运行了。
Observable.create<String> { // it == CreateEmitter
Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
it.onNext("I'm ")
it.onComplete()
}.map {
Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
return@map it + "rx"
}.subscribe { // onNext
Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
Log.d(TAG, it)
}
输出结果:
事件产生线程:main
事件加工线程:main
事件消费线程:main
判断在哪个线程执行问题
理解了线程切换只在subscribeOn或observeOn有切换线程的操作之后,我们再来看看如何判断每个代码块在什么线程执行的问题。这里还要结合上面总结的:1、subscribeOn的线程调度发生在subscribeActual,即订阅阶段。2、observeOn的线程调度则发生在onNext/onError等事件加工/消费阶段。
下面可以看一个稍微复杂一点的例子,我们可以尝试直接通过这个代码判断出所在线程情况:
1、Observable.create传入的事件产生的执行线程
Observable.create的例子中,一般事件产生都是发生在订阅之后,而在subscribeOn的subscribeActual会调用上一级订阅(结合代码,这里source就是ObservableCreate对象),调用其subscribe方法最终会调用ObservableOnSubscribe.subscribe方法,也就是Observable.create传入的这个代码块。经过subscribeOn的subscribeActual后会经历线程切换。故此代码块的调用在Schedulers.io()(RxCachedThreadScheduler-1)的线程。 (ps: 多数情况下,事件产生和订阅不是连续的,所以事件产生也不一定会发生在订阅的线程里。这可能是比较容易混淆的点。)
2、第一个map操作符执行线程
触发map操作符的逻辑会在onNext调用之后,而上一级onNext经过observeOn操作符,这里会经历一次线程切换,也就是说此代码块的调用将会在Schedulers.computation()(RxComputationThreadPool-1)线程进行。
3、第二个map操作符执行线程
参照2的解析,代码块就会在AndroidSchedulers.mainThread()(main)线程执行。
4、最终的事件消费执行线程
事件消费也是发生在onNext的事件流当中,这里可以寻找其往上最近的一个observeOn定义,因为后续的onNext都没有再经过observeOn操作符了,可以判断线程没有再次切换。故最终的事件消费也是在AndroidSchedulers.mainThread()(main)线程。
Observable.create<String> { // it == CreateEmitter
Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
it.onNext("I'm ")
it.onComplete()
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map {
Log.d(TAG, "事件加工线程:${Thread.currentThread().name}")
return@map it + "rx"
}
.observeOn(AndroidSchedulers.mainThread())
.map {
Log.d(TAG, "事件加工线程2:${Thread.currentThread().name}")
return@map "$it!"
}
.subscribe { // onNext
Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
Log.d(TAG, it)
}
输出结果:
事件产生线程:RxCachedThreadScheduler-1
事件加工线程:RxComputationThreadPool-1
事件加工线程2:main
事件消费线程:main
总结:
通过上述的分析来总结一下:
- 订阅阶段是一个自下而上的过程,观察阶段(事件加工/事件消费)是一个自上而下的过程。
- 订阅线程的判断:因为订阅是自下而上的,线程切换的时机也是在subscribeActual方法,所以我们可以找从上往下的第一个subscribeOn操作符来判断线程。
- 观察线程的判断:自上而下的过程,可以找该代码块往上最近的一次observeOn。这也是很多教程提到的所谓map等操作符、事件消费线程observeOn影响的原因了。
subscribeOn的一次有效性问题
经常还会看到很多人说subscribeOn只有第一次设置的有效。那有效是不是代表着就只会切换这一次线程呢?其实上述的介绍已经有提到了,下面让我们用一个图示说明这个问题吧!(为了方便解析,先将事件加工忽略。)
Observable.create<String> { // it == CreateEmitter
Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
it.onNext("I'm ")
it.onComplete()
}
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe { // onNext
Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
Log.d(TAG, it)
}
流程图画出的是在订阅过程中的数据流,可以看到每次调用subscribeActual都会经历一次线程切换。即“第一次设置的有效”的意思并不是只会有一次线程切换,而是因为最终会切换到第一次设置的线程。
最后
本文解析了subscribeOn与observableOn操作符的源码,以及一些常见问题的解析。可以看到subscribeOn与observableOn操作符与线程调度有关,但它们的角色更多的是Rx上面的流程控制。所以我们完全可以将这两个操作符与线程调度scheduler独立开来。后面也会继续写文章总结scheduler的源码解析。
系列文章: