一个简单的 RxJava 例子解读

1,193 阅读6分钟

前言

RxJava 是一个经常让人出错的库,有的同事第一次接触完全搞不懂,个人感觉是没抓住核心的理念(其实我个人对于 RxJava 是有偏见的),抓住核心的理念应该会很好理解,其实就是一个事件流的问题。

当然 RxJava 有许许多多的操作符,使得其更难被理解,但是日常开发中使用的操作符往往就那几个,遇到不熟悉的操作符再去探究也不迟。

RxJava 也经常出现在面试题中,我觉得考察操作符的面试官都是耍流氓···

我这里解析一个简单的例子,让搞不懂 RxJava 的人还有我自己对于 RxJava 有更多的认知,同时顺便也能回答下面这个 面试八股 问题:

subscribeOn 和 observeOn 有什么作用和不同的表现?

一个简单的例子

来看一段极其简单、又包含了日常 Android 开发最常用的操作的代码(这里只引入了 RxJava,所以 Schedulers 没有 Android 相关的东西):

Observable.just(1)
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .subscribe(integer -> System.out.println(integer));

这里其实还有一个问题

Observable.subscribe() 后会有一个返回值 Disposable,需要接收么?为什么?

上面的代码就是处理一个事件(发送一个数字 1),并且调用了 subscribeOn 和 observeOn 来做线程切换。

经常使用 Retrofit 的人会经常写这种代码,因为 Android 的网络请求需要在子线程而 UI 相关的操作又要回到主线程中,所以在 Android 中发网络请求,避不开这两个操作符。

那么,这其中究竟发生了什么呢?

Observable 的创建

其实 just、subscribeOn 等都会帮我们重新创建一个 observable 并且持有前一个。

具体就是:

  1. just 返回一个 observable1
  2. observable1.subscribeOn 又会返回一个 observable2(observable1),observable1 被持有
  3. observable2.observeOn 同理

这个时候你脑海中需要有这样一个图(ObservableJust 为事件源头):

rxjava_1.jpg

那么后续无论你使用什么操作符,基本都是类似的道理,只是链路会越来越长。

代码中 subscribe() 的解释

对于 cold observable,我们需要 subscribe() 来触发事件流。

对应上面代码中的

subscribe(integer -> System.out.println(integer))

这里很简单,只传入了一个 Consumer,什么是 Consumer 呢?很好理解,就是消费事件的,Producer-Consumer 嘛(面试老八股题了,面试官:请实现多种生产者-消费者)。

subscribe 究竟可以传入哪些类型?这里就不多提了,看一下源码就好了。可以传入一个、多个 Consumer,或者干脆你自己来,即传入一个 Observer。

其实方法名也已经很好了,就是让你订阅这个事件,那么你就是要传入一个 Observer(观察者)。只不过 RxJava 这里的 API 是反着来的,Observable.subscribe(Observer)

LambdaObserver

如果你是一个善于发现问题的人,你会觉得有些不对。我传入的是 Consumer 啊,Consumer 和 Observer 也没有任何关系,怎么就是传入了一个 Observer 类型了?

如果传入 Consumer 最终会进入这段代码

 public final Disposable subscribe(@NonNull Consumer<? super T> onNext, @NonNull Consumer<? super Throwable> onError, @NonNull Action onComplete) {
    Objects.requireNonNull(onNext, "onNext is null");
    Objects.requireNonNull(onError, "onError is null");
    Objects.requireNonNull(onComplete, "onComplete is null");
    LambdaObserver<T> ls = new LambdaObserver<>(onNext, onError, onComplete, Functions.emptyConsumer());
    subscribe(ls);
    return ls;
}

所以,其实你还是传入了一个 Observer 类型,叫做 LambdaObserver,它接收多个 Consumer,onNext、onError、onComplete,回调时调用到对应 Consumer 的 apply() 方法,对应示例代码中的就是 System.out.println(integer)(我只传入了一个 Consumer(onNext),示例中是 lambda 表达式,还原回去就是 Consumer 的 apply())

Observable.subscribe()

当调用 Observable.subscribe(Observer) 后,首先 Observable 会持有 Observer,并会调用自身的 subscribeActual(),代码如下:

public final void subscribe(@NonNull Observer<? super T> observer) {
    Objects.requireNonNull(observer, "observer is null");
    try {
        observer = RxJavaPlugins.onSubscribe(this, observer);

        Objects.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins");

        subscribeActual(observer);
    } catch (NullPointerException e) { // NOPMD
        throw e;
    } catch (Throwable e) {
        Exceptions.throwIfFatal(e);
        // can't call onError because no way to know if a Disposable has been set or not
        // can't call onSubscribe because the call might have set a Subscription already
        RxJavaPlugins.onError(e);

        NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");
        npe.initCause(e);
        throw npe;
    }
}

可以看到最终调用 subscribeActual(observer) 并把 observer 传入了。

如果你是一个善于发现问题的人,你会觉得有些不对。

observer = RxJavaPlugins.onSubscribe(this, observer);

这行代码是干啥的?你不好奇吗?其实是开了个 hook 的口子,和正常流程没关系。

这个时候,需要头脑清晰,谨记 Observable 的创建那一小节中的图,source 是层层持有的。

subscribeActual() 由子类重写,各个操作符都不同,但是大体原理相同。示例中是 subscribeOn 和 observeOn 这两个操作符,所以这里就看这两个操作符的 subscribeActual()

Observable.subscribeActual()

其实这两个操作符的 subscribeActual() 基本相似

// ObservableSubscribeOn.java -> subscribeOn
public void subscribeActual(final Observer<? super T> observer) {
    final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);

    observer.onSubscribe(parent);

    parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
// ObservableObserveOn.java -> observeOn
protected void subscribeActual(Observer<? super T> observer) {
    if (scheduler instanceof TrampolineScheduler) {
        source.subscribe(observer);
    } else {
        Scheduler.Worker w = scheduler.createWorker();

        source.subscribe(new ObserveOnObserver<>(observer, w, delayError, bufferSize));
    }
}

先看下面的 observeOn.subscribeActual() 很好理解。

observeOn.subscribeActual()

首先 scheduler.createWorker() 和我们传入的 Scheduler 相关,接着就是调用 source.subscribe() 并传入一个新创建的 ObserveOnObserver(就是个 observer),并且把 subscribeActual(observer) 传入的 observer(这里不是说 ObserveOnObserver 别搞混了) 传进去了。这里的 source 和 传入的 observer 都是谁呢?

本例中,source 就是 subscribeOn 操作符返回的 observable,observer 就是我们 subscribe 传入的 lambdaObserver(这里没法再解释了,这段逻辑就是示例中的几行代码)

简单点就是:给 subscribeOn 的 observable 绑定了个 observer(绑了个观察者(消费者))

其实这里还有一部分 scheduler 的问题,代码中 ObserveOnObserver 构造函数传入了 w(worker) 和 observer。继续看源码,其实就是 observer 执行在了 scheduler 线程中, 具体的流程是当 ObserveOnObserver.onNext() 被调用时,执行了 schedule(),进而执行了 drainNormal(),进而在 scheduler 的某个线程中执行了 downstream.onNext(),downstream 就是我们之前传入的 LambdaObserver,进而获取到了我们的要接收的事件(也就是一个数字 1)

源码流程如下:

// ObserveOnObserver.java
// ObserveOnObserver 构建传入 LambdaObserver 作为 downstream
ObserveOnObserver(Observer<? super T> actual, Scheduler.Worker worker, boolean delayError, int bufferSize) {
  // downstream
  this.downstream = actual;
  this.worker = worker;
  this.delayError = delayError;
  this.bufferSize = bufferSize;
}

// ObserveOnObserver.java
// ObserveOnObserver.onNexr() -> schedule()
public void onNext(T t) {
  if (done) {
      return;
  }

  if (sourceMode != QueueDisposable.ASYNC) {
      queue.offer(t);
  }
  schedule();
}

// ObserveOnObserver.java
// schedule() -> drainNormal()
public void run() {
    if (outputFused) {
        drainFused();
    } else {
        drainNormal();
    }
}

// ObserveOnObserver.java
void drainNormal() {

  final SimpleQueue<T> q = queue;
  final Observer<? super T> a = downstream;

  for (;;) {
      if (checkTerminated(done, q.isEmpty(), a)) {
          return;
      }
      for (;;) {
        // 略
        a.onNext(v);
      }
      // 略
  }
}

到这里,我们唯一不知道的是 onNext() 又是怎么被调用到的?不过不急,再看看 subscribeOn.subscribeActual()

subscribeOn.subscribeActual()

subscribeOn 的具体代码就比较奇怪一些,看不到任何 source.subscribe() 的逻辑,这其实很不合理。因为不注册观察者,又怎么收到通知呢?

其实这里是依靠的 SubscribeTask,scheduleDirect() 会执行我们传入的 task,代码如下:

final class SubscribeTask implements Runnable {
    private final SubscribeOnObserver<T> parent;

    SubscribeTask(SubscribeOnObserver<T> parent) {
        this.parent = parent;
    }

    @Override
    public void run() {
        source.subscribe(parent);
    }
}

source.subscribe() 出现了,这里的 parent 又是谁呢?是 subscribeActual() 时新创建的 SubscribeOnObserver,这个 observer 持有了下游的 observer(observeOn 创建的 observer),source 就是我们的事件源头 Observable.just() 了。

阶段小结

结合 observable 的创建,现在你应该脑海中有这样一个图

rxjava_2.webp

这个时候,我们知道 observable 和 observer 都是层层持有的,然后因为操作符是两个切换线程的操作符,且指定的 scheduler 不同,所以也存在了两个线程池。

但是如何串联起来的呢?ObserveOnObserver.onNext() 又是怎么被调用的呢?

Observer.onNext() 串联

前面已经知道了 subscribeOn 会调用 ObservableJust.subscribe() 方法,因为我们代码里就是 Observable.just(1).subscribeOn()。

而 Observable.subscribe() 核心流程都在 subscribeActual(),所以需要看看 ObservableJust 的 subscribeActual()。

protected void subscribeActual(Observer<? super T> observer) {
    ScalarDisposable<T> sd = new ScalarDisposable<>(observer, value);
    observer.onSubscribe(sd);
    sd.run();
}

可以看到创建了一个 ScalarDisposable(),并调用了 run(),所以看看 run() 的源码

if (get() == START && compareAndSet(START, ON_NEXT)) {
    observer.onNext(value);
    if (get() == ON_NEXT) {
        lazySet(ON_COMPLETE);
        observer.onComplete();
    }
}

这里调用了 observer.onNext() 传入了一个 value(这个 value 其实就是 Observable.just(value),所以也就是数字 1)。

当前处于什么线程呢?

subscribeOn 的 scheduler 的线程。因为我们是通过 SubscribeTask.run(),进而调用了 ObservableJust.subscribeActual()。

observer.onNext 又进而调用了 SubscribeOnObserver.onNext() 进而调用了 ObserveOnObserver.onNext(),终于出现了 ObserveOnObserver.onNext(),前面已经知道了 ObserveOnObserver.onNext() 会调用到我们最终的 LambdaObserver 中。所以至此,整个流程都打通了。

如果你是一个善于发现问题的人,你会觉得有些不对。onCompelete 不是也要调用么?完全没有啊。如果是这两个线程切换操作符的话,在 scheduler 获取不到任务时会发送一个 onCompelete() 进而触发后续流程,其余操作符情况就更加复杂了。

所以到这里,也只是 RxJava 的冰山一角而已。但是相信,如果你之前不是很明白,现在应该是有一些头绪了。

所以这个简单的例子的所有流程串联在一起,就是下面这张图:

rxjava_all.webp

subscribeOn 和 observeOn 操作符的不同

这里只是说前面的那个面试问题,基本就是问你切换线程的表现有何不同,影响谁的线程。

这其实也是 面试八股题 了,其实答案很简单,就是 subscribeOn() 多次调用,事件源头所在的线程是最早的那个(离事件源最近的),observeOn() 多次调用,最终是最晚的那个生效(离观察者最近的)。subscribeOn 影响被观察者线程,observeOn 影响观察者线程。

面试官经常会加问,为什么啊?(其实没什么为什么,源码如此,且这样设计也合理)

我们就强行来看看为什么?

因为 subscribeOn 多次调用后,再执行 subscribe(),只会让 ObservableSubscribeOn(这是个 Observable) 层层持有更靠近事件源的、它前面的一个 observable 并调用其 Observable.subscribe()(准确的说是 subscribeActual())。但是每次 subscribe() 调用后,它都是先在自己的 scheduler 线程中执行了 run()。所以多次调用进入下一个 Observable.subscibe() 时,又切换了线程(这时候还没到事件源)运行,直到传递到事件源的前一个 Observable 时,其 subscribe() 触发了事件源事件,所以事件源执行在最早的 subscribeOn 执行的 scheduler 的线程中。
observeOn 恰恰相反,发生在回调过程中。前面也知道 subscribe() 也会使得各个 observable 创建 observer,observer 层层回调到最后一个 observer,而我们的 observer.onNext() 或者其他方法都是在 observeOn 执行的 scheduler 的线程中的,最后一个 observer 就是我们的 LambdaObserver,所以是最晚的 observeOn 生效。

总结

RxJava 就是这样一个层层嵌套的结构,每一个操作符都会帮你创建一个新的 observable 并内部持有前一个 observable 的引用,直到事件的源头。

对于常见的 cold observable,当你调用 subscribe(lambda) 并且传入一个 lambdaObserver 后才开始启动整个流程。之后因为有层层的 subscribe() 调用,使之创建了众多 observer,并且当前 observer 会持有下游的 observer。当事件源头执行事件后,层层传递,observer 观察到事件后,调用自己的 onNext() 或者其他方法(操作符具体实现)继续通知下游的 observer(downstream),直到回调到一开始我们传入的 LambdaObserver。(本文示例只是最简单的情况,各种操作符实现都不同,但是原理基本相同)