"RxJava 3.x subscribeOn análisis de conmutación de subprocesos"

"RxJava 3.x subscribeOn análisis de conmutación de subprocesos"

Corrutinas de Kotlin

A medida que las corrutinas de Kotlin se vuelven cada vez más estables, se puede decir que los nuevos proyectos básicamente han cambiado a corrutinas, mientras que los proyectos más antiguos se encuentran en un estado de coexistencia de los dos; es previsible que, en lo que respecta a los proyectos de Android , sea solo es cuestión de tiempo antes de que las corrutinas "reemplacen" a RxJava . Las propias ventajas de la rutina son de hecho muy fragantes y pueden satisfacer el desarrollo del proyecto. Lo más importante es la facilidad de uso.

RxJava

Como ilustración de su propia programación receptiva , RxJava sigue siendo un marco de programación receptivo muy bueno, y la última versión se ha actualizado a3.1.6Echo de menos la sorpresa de resolver el infierno de devolución de llamada cuando lo vi por primera vez. Aunque estaba muy familiarizado con él en ese momento, no estudié el pensamiento profundo cuidadosamente después de todo. Dado que el antiguo proyecto ahora está en un estado donde coexisten RxJava y coroutines , también quiero volver a aprender RxJava . Después de todo , hay Todavía hay muchos puntos en común entre RxJava y coroutines .

suscribirse en

RxJava nació para resolver operaciones asincrónicas, y esto se basa en el cambio de subprocesos internos a través de Schedulers . La persona que llama no necesita "preocuparse" por cómo se implementa la implementación interna y puede llamar directamente a subscribeOn(xxx) en una cadena para especificar el subproceso y completar el cambio de subproceso. Entonces, ¿cómo se implementa la capa inferior? Otra pregunta: para varias llamadas a subscribeOn , ¿por qué solo la primera tiene efecto ? Eche un vistazo a la implementación del código fuente con estas dos preguntas.

llamar a subscribeOn varias veces

Omita el análisis de creación directamente. Crear un Observable es relativamente simple. Al crear un operador, tome la creación de una ventana de caracteres como ejemplo. Puede escribirlo así:

private static void subscribeOnTest() {
  Observable.just("Hello World")
    .subscribeOn(Schedulers.newThread())
    .subscribeOn(Schedulers.io())
    .subscribeOn(Schedulers.single())
    .subscribe(new Consumer<String>() {
      @Override
      public void accept(String result) throws Throwable {
        printThreadInfo();
        System.out.println(result);
      }
    })
    sleep(10000L);
}

private static void sleep(long millis) {
  if (millis < 0) return;
  try {
    Thread.sleep(millis);
  } catch () {
    e.printStackTrace();
  }
}

private static void printThreadInfo() {
  Thread thread = Thread.currentThread();
  System.out.println("当前线程名称 = " + thread.getName() + "\n" + "当前线程信息 = " + thread);
}
复制代码

Un método de prueba simple para imprimir información, llamar a subscribeOn varias veces y especificar diferentes datos de operación de subprocesos a través de Schedulers . El propósito de dormir aquí es darle al código de prueba suficiente tiempo para completar la operación. Por supuesto, si este paso es necesario depende de la situación real. Por el IDEA y hardware de esta prueba, si no se suma el tiempo de espera, la información no se puede imprimir ocasionalmente.RxJavaEl sitio web oficial también da una solución. Citando de una respuesta en github :

When you use the default scheduler (Schedulers.computation()) the observable emits on another thread. If your program exits just after the subscribe then the observable is not given a chance to run. Put in a long sleep just after the subscribe() call and you will see it working.

The immediate() scheduler is not safe for recursive scheduling on the current thread, use trampoline() instead.

回到正题。首先需要明确RxJava几个重要“成员”之间的关系。

1. Observabel、Observer、Subscribe

Observable,顾名思义“被观察者”,Observer则是“观察者”;Subscribe订阅操作。有被观察者、观察者,那么需要将它们之间建立关系就必须要订阅这个操作,而subscribe就是让Observable和

Observer建立这种关系的,回忆平时项目代码所写的观察者模式,一般会需要将观察的接口添加到需要观察数据变动的实现类中:

interface Callback {
  void onNotify(String result);
}
//...
private void adCallback(Callback callback) {
  //....
}
复制代码

对于RxJava的链式调用可以简化为:

Observable.just("Hello World").subscribe(observer)
复制代码

按照平时的思维习惯,应该是观察者来订阅被观察者,而从代码所传递的信息上好像是写“反了”,有点别扭,其实也很好理解,意思是一样的:被观察者被观察者订阅。看上面的测试代码,当真正执行到subscribe两者建立关系时才真正是数据流转,线程切换等一些列操作。但前提是冷流。一步步分析内部是怎么执行的。

2. Observable#subscribe

看一下Observable的方法subscribe

@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
public final Disposable subscribe(@NonNull Consumer<? super T> onNext) {
  return subscribe(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION);
}
复制代码

可以看到这里需要的是一个参数Consumer<? super T> onNext,其实就上游数据发送处理后最终回调,说白了就是结果,用来展示刷新UI。具体的实现为:

    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;
    }
复制代码

这个方法里LambdaObserver,是对Observer的一层包装,包含结果、异常、onComplete。重点需要看的是subscribe(ls),本质就是通过这里完成订阅建立联系。

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);
  }
}
//省略了异常捕捉信息
复制代码

这里还是没有具体的实现,但是已经能看到subscribeActual(observer),可以猜测这个方法应该就是真正订阅的方法了。

3. Observable#subscribeActual(observer)

Observable中subscribeActual()方法是抽象的,那么就需要找到具体的实现类,debug模式下可以跟踪到实现类ObservableSubscribeOn,这个类间接继承了Observale抽象类,而**subscribeActual()**具体实现就在这里。代码比较精简,只有一百行,但是确能解释之前提到的问题,直接贴出代码:

package io.reactivex.rxjava3.internal.operators.observable;
import java.util.concurrent.atomic.AtomicReference;
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.internal.disposables.DisposableHelper;
public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
    final Scheduler scheduler;
    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        super(source);
        this.scheduler = scheduler;
    }

  	//subscribe的具体实现方法
    @Override
    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)));
    }
  
    final class SubscribeTask implements Runnable {
        private final SubscribeOnObserver<T> parent;
        SubscribeTask(SubscribeOnObserver<T> parent) {
            this.parent = parent;
        }
        @Override
        public void run() {
            source.subscribe(parent);
        }
     }

    static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable {
        private static final long serialVersionUID = 8094547886072529208L;
        final Observer<? super T> downstream;
        final AtomicReference<Disposable> upstream;
        SubscribeOnObserver(Observer<? super T> downstream) {
            this.downstream = downstream;
            this.upstream = new AtomicReference<>();
        }
        @Override
        public void onSubscribe(Disposable d) {
            DisposableHelper.setOnce(this.upstream, d);
        }
        @Override
        public void onNext(T t) {
            downstream.onNext(t);
        }
        @Override
        public void onError(Throwable t) {
            downstream.onError(t);
        }
        @Override
        public void onComplete() {
            downstream.onComplete();
        }
        @Override
        public void dispose() {
            DisposableHelper.dispose(upstream);
            DisposableHelper.dispose(this);
        }
        @Override
        public boolean isDisposed() {
            return DisposableHelper.isDisposed(get());
        }
        void setDisposable(Disposable d) {
            DisposableHelper.setOnce(this, d);
        }
    }
}
复制代码

看关键代码 subscribeActual(final Observer<? super T> observer) 的第一行:

final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);
复制代码

这里传入的观察者observer就是上述测试代码的回调Consumer,通过这个observer又重新构造了一个SubscribeOnObserver对象,并且命名为parent;这个命名也是有讲究的。这个放到后面在讲。看一下SubscribeOnObserver的构造函数:

SubscribeOnObserver(Observer<? super T> downstream) {
  this.downstream = downstream;
  this.upstream = new AtomicReference<>();
}
复制代码

很好理解观察者observer,就是下游的数据downstream,同理上游是谁?就是被观察者Observable,不过此时上游的数据还没有下发下来。总结一句话:利用下游的observer重新构建了一个SubscribeOnObserver,其实本质还是observer,只不过新构建的这个变成parent?What the hell?;这是什么操作?不按常理出牌啊,不应该是child才对嘛?其实不然,从这里其实可以猜到一点了,我们知道的是数据从上游发送到下游。数据的处理却是从下游到上游的。稍后验证,看看第二行关键代码:

observer.onSubscribe(parent);
复制代码

回忆之前observable#subscribe方法,从订阅开始,其实是利用Consumer创建了一个LambdaObserver对象,也即是这里的observer,所以去到LambdaObserver类中看看LambdaObserver#onSubscribe的具体实现:

observer.onSubscribe(parent);
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<>(observer);
//...
@Override
public void onSubscribe(Disposable d) {
  if (DisposableHelper.setOnce(this, d)) {
    try {
      onSubscribe.accept(this);
    } catch (Throwable ex) {
      Exceptions.throwIfFatal(ex);
      d.dispose();
      onError(ex);
    }
  }
}
//...
public static boolean setOnce(AtomicReference<Disposable> field, Disposable d) {
  Objects.requireNonNull(d, "d is null");
  if (!field.compareAndSet(null, d)) {
    d.dispose();
    if (field.get() != DISPOSED) {
      reportDisposableSet();
    }
    return false;
  }
  return true;
}
复制代码

需要注意的是,由于此时均还是默认创建的observer,比较简单直接走到方法内部onSubscribe.accept(this);。直接看最后一行的行为:

parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码

从创建SubscribeTask开始,分析一下这个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);
  }
}
复制代码

实现了Runnable接口,也就是为开了一个子线程来执行任务,重要的是用来执行什么任务的?也就是source.subscribe(parent);,这里的parent好理解,上面已经解释过了,至于source是什么,先放放。目前仅仅需要了解的它就是一个线程任务,那么这个任务的执行时机是什么时候呢?

parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码

在看scheduleDirect,调用者是scheduler,也就是线程调度器。scheduleDirect翻译过来的字面意思就是直接“执行”。看看实现是不是这样,找到Ssheduler#scheduleDirect:

@NonNull
public Disposable scheduleDirect(@NonNull Runnable run) {
  return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
@NonNull
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
  final Worker w = createWorker();
  final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
  DisposeTask task = new DisposeTask(decoratedRun, w);
  w.schedule(task, delay, unit);
  return task;
}
//worker 为Scheduler中的抽象类
复制代码

和分析的是一样这个方法就是让task直接运行,执行什么?就是SubscribeTask#run的实现也就是source.subscribe(parent);。而调用者也知道了就是scheduler

parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
复制代码

但是scheduler是抽象的,所以需要知道它的实现类,其实就是**scheduler.io()**等对应的那些操作,主要实现类:

//ComputationScheduler
//ExecutorScheduler
//ImmediateThinScheduler
//IoScheduler
//NewThreadScheduler
//SchedulerWhen
//SingleScheduler
//TestScheduler
//TrampolineScheduler
复制代码

到这里基本就要搞清楚scheduler的实际实现类就清楚了。回到开始测试的代码:

  Observable.just("Hello World").subscribeOn(Schedulers.newThread())..subscribeOn(Schedulers.io())
  
  public final Observable<T> subscribeOn(@NonNull Scheduler scheduler) {
  Objects.requireNonNull(scheduler, "scheduler is null");
  return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<>(this, scheduler));
}

  public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
    super(source);
    this.scheduler = scheduler;
  }
复制代码

可就清晰了,当调用到最后一个 .subscribeOn(Schedulers.single()) 此时的scheduler 已经是 Schedulers.single() 了;那么问题来了线程是怎么从 single 一步步切换到第一次的调用 newThread() 的呢?将这个链式切换线程的方法展开从订阅的动作触发开始就相当于(回忆上述的parent)将数字越大的则记为“辈分”越小,那么整个流程展开的话就是

Observer observer4 = new LambdaObserver()
SubscribeOnObserver parent3 = new SubscribeOnObserver(observer4)  
//通过Runnable任务
parent3.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent3)));
--> 这个方法的调用者是ObservableSubscribeOn也就是可以理解为observable 进一步简化为
--> observable2.subscribeActual(parent3) 从头开始就是 

Observer observer4 = new LambdaObserver()
observable3.subscribeActual(observer4) [SubscribeOnObserver parent3 = new SubscribeOnObserver(observer4)]
observable2.subscribeActual(parent3)
observable1.subscribeActual(parent2)
observable.subscribeActual(parent1)

//这样整个流程就完成了从最下游的 Schedulers.single(),递归的到了第一次的Schedulers.newThread()
复制代码
伪代码

注意到ObservableSubscribeOn的抽象父类AbstractObservableWithUpstream除了继承了Observable;还实现了接口HasUpstreamObservableSource,这个接口很好解释了SubscribeTask是如何工作流转的。看看这个接口的解释:

//Interface indicating the implementor has an upstream ObservableSource-like source available //via {@link #source()} method.
public interface HasUpstreamObservableSource<@NonNull T> {
  //Returns the upstream source of this Observable.
  //Allows discovering the chain of observables.
  //@return the source ObservableSource
  @NonNull
  ObservableSource<T> source();
}
复制代码
  • AbstractObservableWithUpstream
abstract class AbstractObservableWithUpstream<T, U> extends Observable<U> implements HasUpstreamObservableSource<T> {

    /** The source consumable Observable. */
    protected final ObservableSource<T> source;

    /**
     * Constructs the ObservableSource with the given consumable.
     * @param source the consumable Observable
     */
    AbstractObservableWithUpstream(ObservableSource<T> source) {
        this.source = source;
    }

    @Override
    public final ObservableSource<T> source() {
        return source;
    }
}
复制代码

那么就比较清晰了,每次创建新的ObservableSubscribeOn;既然是抽象的继承关系,必然会先走的父类的构造方法,而父类中又持有了上游的upStream通过接口HasUpstreamObservableSource。通过伪代码展示一下是不是这样,剔除其他所有方法,仅仅保留这个source

public interface HasUpstreamObservableSource {
    ObservableSource source();
}

public abstract class AbstractObservableWithUpstream extends Observable implements HasUpstreamObservableSource {
    protected final ObservableSource source;
    AbstractObservableWithUpstream(ObservableSource source) {
        this.source = source;
    }
    @Override
    public ObservableSource source() {
        return source;
    }
}

public abstract class Observable implements ObservableSource {
    @Override
    public final void subscribe(Observer observer) {

    }
}

interface ObservableSource {
     void subscribe(Observer observer);
}

public class ObservableSourceImpl implements ObservableSource {
    public ObservableSourceImpl() {

    }
    @Override
    public void subscribe(Observer observer) {

    }
}

public class ObservableSubscribeOn extends AbstractObservableWithUpstream {
    public  ObservableSubscribeOn(ObservableSource source) {
        super(source);
    }

    static final class SubscribeOnObserver implements Observer {
        SubscribeOnObserver () {

        }

        @Override
        public void onSubscribe() {

        }

        @Override
        public void onNext() {

        }

        @Override
        public void onError() {

        }

        @Override
        public void onComplete() {

        }
    }

    final class SubscribeTask implements Runnable {

        private final SubscribeOnObserver parent;

        SubscribeTask(SubscribeOnObserver parent) {
            this.parent = parent;
        }

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

    @Override
    public String toString() {
        return super.toString();
    }
}

interface Observer {
    void onSubscribe();

    void onNext();

    void onError();

    void onComplete();
}
复制代码
  • 测试打印信息
   
private static void test() {
  ObservableSourceImpl observableRoot = new ObservableSourceImpl();
  System.out.println("observableRoot = " + observableRoot);
  ObservableSubscribeOn one = new ObservableSubscribeOn(observableRoot);
  System.out.println("one = " + one);
  ObservableSubscribeOn two = new ObservableSubscribeOn(one);
  System.out.println("two = " + two);
  ObservableSubscribeOn three = new ObservableSubscribeOn(two);
  System.out.println("three = " + three);
  ObservableSubscribeOn four = new ObservableSubscribeOn(three);
  System.out.println("four = " + four);
  
  
  System.out.println("------------>\n");
  System.out.println("four 前置 ObservableSource = " + four.source());
  System.out.println("three 前置 ObservableSource = " + three.source());
  System.out.println("two 前置 ObservableSource = " + two.source());
  System.out.println("one 前置 ObservableSource = " + one.source());
}

//打印信息

> Task :main()
observableRoot = io.reactivex.rxjava3.simple.ObservableSourceImpl@1c4af82c
one = io.reactivex.rxjava3.simple.ObservableSubscribeOn@76ccd017
two = io.reactivex.rxjava3.simple.ObservableSubscribeOn@182decdb
three = io.reactivex.rxjava3.simple.ObservableSubscribeOn@26f0a63f
four = io.reactivex.rxjava3.simple.ObservableSubscribeOn@4361bd48
------------>

four 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@26f0a63f
three 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@182decdb
two 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSubscribeOn@76ccd017
one 前置 ObservableSource = io.reactivex.rxjava3.simple.ObservableSourceImpl@1c4af82c
复制代码

可以看到的是,每一个ObservableSource都持有了它的上游ObservableSource,就是套娃,而持有的目的是什么呢?想想之前的SubscribeTask中的实现:

source.subscribe(parent);
复制代码

这里的source就是对其上游的引用,那就好理解了,这个task任务就是为了激活它的上游。因此回忆之前的just发射数据,订阅之前的最后一次调用subscribeOn(Schedulers.single()),那么这个task在哪里执行呢,这个跟被包装的Schedulers有关,这个因为不仅仅能拿到source信息,同样可以拿到schedulers的信息。知道遇到真正发射数据的observable,所以这个时候就意味着整个由下至上的流程就结束了,也就是只有第一次的设置是“有效的”。其他的不是没有效果,只是对于用户无感知。

总结

整个流程下来可以清晰的看到,当被观察者Observable,被创建出来是,通过subscribeOn多次调用切换线程,最终被订阅subscribe到观察者上,但是处理订阅流是从下而上的,类似递归的方式,将observable一直传到最下游,每一级的结果依赖下层的返回值,而这个过程中observavle的功能被定制化了,包括线程切换(需要在什么线程下来处理数据等)。直到递归的出口,最终决定数据在什么线程中被处理。最后回调到Consumer中的accept方法抛出处理后的数据。仔细思考一下,这种方式设计确实很优雅,创建observableobserver是初期就确定的,通过订阅操作subscribe将两者建立起关系。数据发射一层层包装到最下游,当“知道”下游需要什么的样数据、需要在什么线程处理,在一层层由下至上拿到每一层的定制化需要最终返回结果。所以做了这么多次的线程切换表明上看只有第一次成功了, es en realidad el resultado final del cambio continuo a través de SubscribeTask internamente , no es que otros cambios no hayan "surtido efecto".

Supongo que te gusta

Origin juejin.im/post/7215217068802949177
Recomendado
Clasificación