RxJava2如何实现链式调用和线程切换

线程切换

背压

背压在计算机系统中指网络拥塞信息逆流通过。在rxjava中理解为:被观察者发送消息太快以至于它的操作符或者订阅者不能及时处理相关的消息,从而操作消息的阻塞现象

在RxJava2.0中官方,推出了Flowable 和Subscriber用来支持背压,同样的去除了Observable对背压的支持,对的就像你上面看到的,Observable不再支持背压了,即使阻塞崩溃也不会抛出MissingBackpressureException

Flowable的用法吧。

Flowable.create(FlowableOnSubscribe source, BackpressureStrategy mode)

FlowableOnSubscribe很好理解就是一个就是Flowable的一个被观察者源,而BackpressureStrategy就是Flowable提供的背压策略

背压策略

  1. MISSING:
    如果流的速度无法保持同步,可能会抛出MissingBackpressureException或IllegalStateException。
  2. BUFFER
    上游不断的发出onNext请求,直到下游处理完,也就是和Observable一样了,缓存池无限大,最后直到程序崩溃
  3. ERROR
    会在下游跟不上速度时抛出MissingBackpressureException。
  4. DROP
    会在下游跟不上速度时把onNext的值丢弃。
  5. LATEST
    会一直保留最新的onNext的值,直到被下游消费掉。

链式调用

变换/操作符
  1. 所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。比如要修改Observable对象,把字符串改为整型或者加上另一串字符等等。
  2. 操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observable发出的事件。
  3. 变换操作符API
  • from()
    Observable.from()方法,它接收一个集合作为输入,然后每次输出一个元素给subscriber
Observable.from("url1", "url2", "url3")
.subscribe(new Action1() { 
        @Override  
        public void call(String s) { 
            System.out.println(s); 
        }  
    });  
  • map()改变observable对象发出的数据类型
    map操作符有趣的一点是它不必返回Observable对象返回的类型,我们可以使用map操作符返回一个发出新的数据类型的observable对象。
    比如上面的例子中,subscriber如果并不关心返回的字符串,而是想要字符串的hash值,则可以这样写:
Observable.just("Hello, world!")    //输入类型 String
    .map(new Func1() {  //String ---> Integer
        @Override  
        public Integer call(String s) {  //参数类型 String
            return s.hashCode();        //返回类型 Integer
        }  
    })  
    .subscribe(new Action1() {  //由map()返回的Observable对象调用
        @Override  
        public void call(Integer i) {   //参数类型 Integer(即上面的返回类型)
              System.out.println(Integer.toString(i));  
        }  
    });  

这里出现了一个叫做 Func1 的类。它和 Action1 非常相似,也是 RxJava 的一个接口,用于包装含有一个参数的方法。 Func1 和 Action 的区别在于,** Func1 包装的是有返回值的方法**。另外,和 ActionX 一样, FuncX 也有多个,用于不同参数个数的方法。(用于输入与输出参数数据类型的变换)

  • flatMap()
    flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中map()解决的是一对一的变换,flatMap()解决的是一对多的变换。

flatMap()的原理

  1. 使用传入的事件对象创建一个 Observable 对象;
  2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
  3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable,而这个 Observable 负责将这些事件统一交给Subscriber 的回调方法。
Student[] students = ...;
Subscriber subscriber = new Subscriber() {
    @Override
    public void onNext(Course course) {
        Log.d(tag, course.getName());       //打印课程名
    }
    ...
};
Observable.from(students)
    .flatMap(new Func1>() {
        @Override
        public Observable call(Student student) {     //输入参数 student
            return Observable.from(student.getCourses());        //返回student对应课程 Observable
        }
    })
    .subscribe(subscriber);

以上例程通过flatMap() 实现打印每一个student的课程(一个student对应多个课程)。

变换的原理 lift()

这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在 RxJava 的内部,它们是基于同一个基础的变换方法: lift(Operator)。

在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。不建议自定义Operater()来直接使用lift()

  • 图解

RxJava2如何实现链式调用和线程切换_第1张图片

  • lift() 创建了一个 Observable 后,加上之前的原始 Observable,已经有两个 Observable 了;
  • 而同样地,新 Observable 里的新 OnSubscribe 加上之前的原始 Observable 中的原始 OnSubscribe,也就有了两个 OnSubscribe;
  • 当用户调用经过 lift() 后的 Observable 的 subscribe() 的时候,使用的是 lift() 所返回的新的 Observable ,于是它所触发的 onSubscribe.call(subscriber),也是用的新 Observable 中的新 OnSubscribe,即在 lift() 中生成的那个 OnSubscribe;
  • 而这个新 OnSubscribe 的 call() 方法中的 onSubscribe ,就是指的原始 Observable 中的原始 OnSubscribe ,在这个 call() 方法里,新 OnSubscribe 利用 operator.call(subscriber) 生成了一个新的 Subscriber(Operator 就是在这里,通过自己的 call() 方法将新 Subscriber 和原始 Subscriber 进行关联,并插入自己的『变换』代码以实现变换),然后利用这个新 Subscriber 向原始 Observable 进行订阅。
    这样就实现了 lift() 过程,有点像一种代理机制,通过事件拦截和处理实现事件序列的变换。
  1. compose: 对 Observable 整体的变换
  • 它和 lift() 的区别在于,lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable 自身进行变换。
  • 假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。可以这么写:
public class LiftAllTransformer implements Observable.Transformer {
    @Override
    public Observable call(Observable observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

线程切换

在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于 RxJava 是至关重要的。
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件.如果需要切换线程,就需要用到 Scheduler (调度器)

  1. Schedule的API
  • Schedule的几个线程调用函数(5个):
  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的schedule。
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数.不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • AndroidSchedulers.mainThread(),Android 专用的,它指定的操作将在 Android 主线程运行。

有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了

  • subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
  • observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。即做出响应的线程。

例程:

Observable.just(1, 2, 3, 4).subscribeOn(Schedulers.io())     // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread())       // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);   //发生在主线程
}
});

线程控制(二):Scheduler

  • 上面讲到可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。又知道map(),和flatMap()等可以做不同的变换,那么也可以通过多次地调用函数来实现多次线程的切换
Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

通过 observeOn() 的多次调用,程序实现了线程的多次切换。不过,不同于 observeOn() , subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。(因为只有第一次的SubscribeOn()是有效的)
RxJava2如何实现链式调用和线程切换_第2张图片

RxJava2如何实现链式调用和线程切换_第3张图片

  • subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的 “schedule…” 部位)。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn()控制的是它后面的线程。

  • 上游事件是怎么给弄到子线程里去的?
    就是直接把订阅方法放在了一个Runnable中去执行,这样就一旦这个Runnable在某个子线程执行,那么上游所有事件只能在这个子线程中执行了。

  • 线程间传递消息也是使用 Handler来实现。

延伸:doOnSubscribe()
虽然超过一个的 subscribeOn() 对事件处理的流程没有影响,但在流程之前却是可以利用的。
onStart() 由于在 subscribe() 发生时就被调用了,因此不能指定线程,而是只能执行在 subscribe() 被调用时的线程。这就导致如果 onStart() 中含有对线程有要求的代码(例如在界面上显示一个 ProgressBar,这必须在主线程执行),将会有线程非法的风险,因为有时你无法预测 subscribe() 将会在什么线程执行。而与 Subscriber.onStart() 相对应的方法 Observable.doOnSubscribe() 。它和Subscriber.onStart() 同样是在 subscribe() 调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下, doOnSubscribe() 执行在 subscribe() 发生的线程;而如果在 doOnSubscribe() 之后有subscribeOn() 的话,它将执行在离它最近的 subscribeOn() 所指定的线程。

Observable.create(onSubscribe)
    .subscribeOn(Schedulers.io())    //事件的发出还是在io线程
 
    .doOnSubscribe(new Action0() {
        @Override
        public void call() {
            progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
        }
    })
    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);

subscribeOn() 的线程切换原理

@CheckReturnValue
    @SchedulerSupport(SchedulerSupport.CUSTOM)
    public final Observable subscribeOn(Scheduler scheduler) {
        //非空判断,若为空会直接抛出异常
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        //这里将Observable和Scheduler封装成了ObservableSubscribeOn
        //onAssembly方法返回了这个new ObservableSubscribeOn(this, scheduler)对象
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn(this, scheduler));
    }
  • 首先subscribeOn方法中将Observable和Scheduler封装成了ObservableSubscribeOn(是Observable的子类)。返回了一个新的Observable,而这个新的Observable里面持有一个上一层Observable的引用

  • 接着,订阅的时候调用subscribe方法,在subscribe方法中会调用封装的ObservableSubscribeOn的subscribeActual()方法(方法里面会先调用observer的onSubscribe()方法,此时的Observable的subscribe方法发生在当前线程,所以Observer的onSubscribe方法的执行线程和当前调用Observable的subscribe方法的线程一致!!!

public void subscribeActual(Observer s) {
    ObservableSubscribeOn.SubscribeOnObserver parent = new ObservableSubscribeOn.SubscribeOnObserver(s);
    s.onSubscribe(parent);
    parent.setDisposable(this.scheduler.scheduleDirect(new ObservableSubscribeOn.SubscribeTask(parent)));
}

  • 里面new了一个SubscribeTask, ObservableSubscribeOn.SubscribeTask(parent)(ObservableSubscribeOn.SubscribeTask是一个Runnable),实现了Runnable接口,它的run方法会在subscribeOn指定的Scheduler的线程中执行。当我们的 SubscribeTask 的 run 方法运行在哪个线程,相应的 observable 的 subscribe 方法就运行在哪个线程。

  • SubscribeTask 包装 parent(SubscribeOnObserver ,包装了 Observer),SubscribeTask 实现了 Runnable 接口,在 run 方法里面调用了 source.subscribe(parent),因而 run 方法所执行的线程将由 worker 决定。这就是 下游决定上游 observable 执行线程的原理

observeOn() 的线程切换原理

@CheckReturnValue
@SchedulerSupport("custom")
public final Observable observeOn(Scheduler scheduler) {
    return this.observeOn(scheduler, false, bufferSize());
}
@CheckReturnValue
@SchedulerSupport("custom")
public final Observable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    ObjectHelper.verifyPositive(bufferSize, "bufferSize");
    return RxJavaPlugins.onAssembly(new ObservableObserveOn(this, scheduler, delayError, bufferSize));
}

同样地

  • 首先observeOn方法中将Observable和Scheduler封装成了ObservableObserveOn(是Observable的子类)。返回了一个新的Observable,而这个新的Observable里面持有一个上一层Observable的引用
  • 接着,订阅的时候调用subscribe方法,在subscribe方法中会调用封装的ObservableObserveOn的subscribeActual()方法
protected void subscribeActual(Observer observer) {
        if (this.scheduler instanceof TrampolineScheduler) {
            this.source.subscribe(observer);
        } else {
            Worker w = this.scheduler.createWorker();
            this.source.subscribe(new ObservableObserveOn.ObserveOnObserver(observer, w, this.delayError, this.bufferSize));
        }

    }
  • 里面new了一个ObserveOnObserver对象,它的onNext、onComplete、onError方法里面都调用了schedule方法。
void schedule() {
        if (this.getAndIncrement() == 0) {
            this.worker.schedule(this);
        }

}
  • 执行worker的schedule方法,其中worker是我们调用observeOn(Schedule)方法时传入的Schedule的Worker,比如IoSchedule的ThreadWorker,最终,worker会执行其scheduleActual方法。最后无非是用线程池提供一个线程,执行ObservableObserveOn.ObserveOnObserver的run方法,ObservableObserveOn.ObserveOnObserver是实现了Runnable接口的。

总结:

  1. 子线程切换主线程:给主线程所在的Handler发消息,然后就把逻辑切换过去了。

  2. 主线程切换子线程:把任务放到线程池中执行就能把执行逻辑切换到子线程

  3. 子线程切换子线程:把任务分别扔进两个线程就行了。

为什么 subscribeOn() 只有第一次切换有效

因为 RxJava 最终能影响 ObservableOnSubscribe 这个匿名实现接口的运行环境的只能是最后一次运行的 subscribeOn() ,又因为 RxJava 订阅的时候是从下往上订阅,所以从上往下第一个 subscribeOn() 就是最后运行的,这就造成了写多个 subscribeOn() 只有第一个subscribeOn()有用。

https://www.jianshu.com/p/88aacbed8aa5

RxJava2如何实现链式调用和线程切换_第4张图片

subscribeOn()产生的线程切换发生在代码执行的第二层,而它的回溯又将会执行在新的线程中。因此,在subscribeOn切换线程以后的流程,均将在新的线程中执行。

RxJava2如何实现链式调用和线程切换_第5张图片

observeOn()产生的线程切换都发生在第三层执行层,而在切换线程前的业务代码由于已经执行了,故不受切换线程切换的影响。

你可能感兴趣的:(第三方库,android,多线程,Rxjava)