RxJava除了要知道它的核心思想和基本概念我们还应该弄明白什么呢?必然是原理级别的啦,为什么RxJava可以使用链式调用,在源码中这个链式调用是怎么体现的?RxJava是如何实现线程切换的,以及为什么设置线程规则时subscribeOn只有第一次设置生效,而observeOn可以设置多次生效?RxJava2中的背压策略是什么?这些问题都是本章我们需要一一去研究弄明白的知识点。
熟悉或者写过构建者模式的同学可能会更清楚一点链式调用。就是每执行一个方法都会返回一个对应的对象来达到依次调用的效果。通常构建者模式都是返回Builder自身,最后执行build方法时才返回构建完成的实例对象。这一设计模式在Android源码中也能够随处可见,足以说明它的重要性。RxJava其实也同样采用了这种设计模式,只不过它在这种模式的基础上又结合了装饰器模式,两种模式结合在一起来达到链式调用的效果(也就是我们下面图解中的洋葱模型)。说这么多,大家还是懵逼的。下面我们通过图解可能会更好理解一点。
跟过源码的同学可能会发现Observable的很多操作符都会对应返回一个Observable对象,我们还是看图比较容易理解一些。
从上图就能狠清晰的看出来Observable.create()返回的是一个ObservableCreate的对象。那么这个ObservableCreate是个什么东西呢?看一下它的源码:
通过源码就狠清楚知道,这个ObservableCreate同样是个Observable,而我们接下来再调用的操作符subscribeOn就是在这个Observable对象基础上调用的,同理subscribeOn、observeOn等操作符都会返回一个重新进行包装过后的新的Observable对象(也就是上图中的洋葱模型了),直到最后调用subcribe产生订阅。那么这种模型是如何实现订阅关系的呢?我们还是通过图来看更清晰一些,毕竟只看源码很枯燥的,看懂图片再去跟源码或许会轻松很多。
从RxJava的订阅模型就能看出来,我们在代码中最后创建的观察者订阅的是最外层的Observable,而数据流(事件)最终是通过这种逐级传递的方式到达我们最内层的观察者。当然了,这里的逐级传递还涉及到了很多的知识点,如下面我们要讲的线程切换就是其中之一。
在RxJava中进行线程控制都是通过设置线程调度器来实现的,每一种调度器都有着其独特的功能特性和使用场景。
调度器类型 |
效果 |
Schedulers.computation() |
用于计算任务,如事件循环或和回调处理,不要用于IO操作(IO操作请使用Schedulers.io());默认线程数等于处理器的数量 |
Schedulers.from(executor) |
使用指定的Executor作为调度器 |
Schedulers.immediate( ) |
在当前线程立即开始执行任务 |
Schedulers.io( ) |
用于IO密集型任务,如数据库读写,网络请求等 |
Schedulers.newThread( ) |
为每个任务创建一个新线程 |
Schedulers.trampoline( ) |
当其它排队的任务完成后,在当前线程排队开始执行 |
AndroidSchedulers.mainThread() |
用于Android的UI更新操作,任务执行在主线程中,一帮设置为observeOn的调度器,用于下游接收到数据流后更新UI |
上图是我扣来的一个RxJava整个调用流程以及线程切换流程。如果有同学能一眼就看明白这张图,那就真的很厉害了。反正我一开始是没看明白的,跟了狠久的源码才稍微弄明白了一点点。那么下面我就根据这张图以及源码来讲一下我弄明白的那一点点。这里的map等操作符都会返回一个新包装的Observable对象前面已经说过了,那么我们来从下往上看它是如何实现线程切换的。
a、observeOn() 的线程切换原理
根据运行流程来看 observeOn() 执行后是得到 ObservableObserveOn 对象,那么当 ObservableObserveOn 绑定监听者的时候要运行 subscribe() 方法
public final void subscribe(Observer super T> observer) {
ObjectHelper.requireNonNull(observer, "observer is null");
try {
observer = RxJavaPlugins.onSubscribe(this, observer);
ObjectHelper.requireNonNull(observer, "Plugin returned null Observer");
//调用 subscribeActual()
subscribeActual(observer);
} catch (NullPointerException e) { // NOPMD
throw e;
} catch (Throwable e) {
...
}
}
这里的subscribeActual方法里面又干了什么呢?
protected void subscribeActual(Observer super T> observer) {
if (scheduler instanceof TrampolineScheduler) {
source.subscribe(observer);
} else {
//scheduler 是传进来的线程调度对象,如 Schedulers.io() 、AndroidSchedulers.mainThread() 等,这里调用了 createWorker() 方法暂时看一下就好稍后分析 RxAndroid 会说明
Scheduler.Worker w = scheduler.createWorker();
//我们看到他把 w 参数传进去了
source.subscribe(new ObserveOnObserver(observer, w, delayError, bufferSize));
}
}
从上述源码我们可以看到 ObservableObserveOn 是被 ObserveOnObserver 监听的,所以收到通知也是由 ObserveOnObserver 作出响应,接下来我们假设当 Rxjava 发送 onNext 通知时会调用 ObserveOnObserver 的 onNext() 方法 (当然如果是 onComplete()、onError() 等也是一样的逻辑 ),然后我们来看一看 ObserveOnObserver 的 onNext() 方法
@Override
public void onNext(T t) {
if (done) {
return;
}
if (sourceMode != QueueDisposable.ASYNC) {
queue.offer(t);
}
//切换线程
schedule();
}
void schedule() {
if (getAndIncrement() == 0) {
//直接调用了 worker 的 schedule 方法,需要注意的是这里他把自己传了进去
worker.schedule(this);
}
}
现在我先把把 schedule(Runnable run) 贴出来
public Disposable schedule(@NonNull Runnable run) {
return schedule(run, 0L, TimeUnit.NANOSECONDS);
}
1、我们看到这个他接收的参数是一个 Runnable,这是怎么回事呢,我们看一下 ObserveOnObserver 对象,他不但实现了 Observer 接口并且也实现了 Runnable 接口
2、接下看,继续调用 schedule( Runnable action, long delayTime, TimeUnit unit) 方法,但是这个方法是个抽象方法,这里我们就假设这里这个 worker 是 IO 线程,所以我直接贴 IoScheduler 的代码了
public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) {
if (tasks.isDisposed()) {
// don't schedule, we are unsubscribed
return EmptyDisposable.INSTANCE;
}
return threadWorker.scheduleActual(action, delayTime, unit, tasks);
}
然后再贴一下 scheduleActual 的方法
public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) {
Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
//就是个 Runnable
ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent);
if (parent != null) {
if (!parent.add(sr)) {
return sr;
}
}
Future> f;
try {
//判断延迟时间,然后使用线程池运行 Runnable。看到这里是不是有点熟悉呢?有没有感觉和AsyncTask有点像呢,都是利用线程池来完成线程间的切换。
if (delayTime <= 0) {
f = executor.submit((Callable
这样一来就会在相应的线程中运行 ObserveOnObserver 的 run 方法
b、subscribeOn() 的线程切换原理
这个切换原理其实和 observeOn() 原理很像,只不过这个操作的对象是 ObservableSubscribeOn, 这个对象也是同样的代码逻辑,运行 subscribe() 方法,然后调用 subscribeActual() 方法,所以这里就直接贴 subscribeActual() 的源码了
public void subscribeActual(final Observer super T> s) {
//创建与之绑定的 SubscribeOnObserver
final SubscribeOnObserver parent = new SubscribeOnObserver(s);
s.onSubscribe(parent);
//1. 创建 SubscribeTask 实际上就是个 Runnable
//2. 然后调用 scheduler.scheduleDirect 方法
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
我们看一下 scheduleDirect 的方法
public Disposable scheduleDirect(@NonNull Runnable run) {
return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
final Worker w = createWorker();
final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
//一个 Runnable 具体作用没分析
DisposeTask task = new DisposeTask(decoratedRun, w);
//这个代码看着熟悉吗 没错上面 observeOn 提到过,知道它是运行 Runnable 我们就直接看 Runnable 里面的 run() 了
w.schedule(task, delay, unit);
return task;
}
我们看一下 DisposeTask 的 run()
public void run() {
runner = Thread.currentThread();
try {
decoratedRun.run();
} finally {
dispose();
runner = null;
}
}
调来调去我们又回到了 SubscribeTask 的 run()
public void run() {
source.subscribe(parent);//这里执行的就是ObservableOnSubscribe的subscribe方法
}
这个地方的运行线程已经被切换了,他又开始往上一层层的去订阅,所以 create(new ObservableOnSubscribe
因为 RxJava 最终能影响 ObservableOnSubscribe 这个匿名实现接口的运行环境的只能是最后一次运行的 subscribeOn() ,又因为 RxJava 执行subcribe订阅的时候是从下往上订阅(洋葱模型的最外层到最内层),所以我们所写的第一个subscribeOn()也就变成了最后一个运行的subscribeOn()。而observeOn却相反,数据流的传递是从上往下(洋葱模型的从内而外)的传递,这个时候每遇到一个observeOn都会生效一次。
观察者 & 被观察者 之间存在2种订阅关系:同步 & 异步。
同步订阅:就是上游(被观察者)数据发射与下游(观察者)数据接收都在同一个线程中。
异步订阅:就是上游(被观察者)数据发射与下游(观察者)的数据接收不在同一个线程中,这时候就有可能会出现一种被观察者发送事件速度 与观察者接收事件速度 不匹配的情况。
被观察者 发送事件速度太快,而观察者 来不及接收所有事件,就会导致观察者无法及时响应 / 处理所有发送过来事件的问题,最终导致缓存区溢出、事件丢失 & OOM等情况。在RxJava2.0之前是没有这种问题解决方案的,RxJava2的时候增加了背压策略来解决这种速度不匹配导致的问题。那么什么是背压策略呢?
我们一直说RxJava是一种链式响应、基于事件流的。而上述问题的出现也是由于事件流的发射速度与接收处理速度不匹配导致的。那么背压策略就是一种控制事件流速的策略。至于RxJava2.0是如何实现这种流速控制的,这里推荐这位大神的博文,这是我看到的写的最全面的一篇关于背压策略的文章了。我就不在这里献丑了。接下来几章会详细介绍RxJava中的操作符。