继上篇,关于Rxjava,让你知道什么叫“大海无量”

上一节我们知道subscribeOn是指定它上游的observable订阅发生的线程,而doOnSubscribe操作符最终也是生成了一个ObservableDoOnLifecycle的observable,所以可以这么说ObservableDoOnLifecycle的订阅发生的线程是由紧跟它后面的subscribeOn指定的线程所决定的。

而在ObservableDoOnLifecycle的订阅方法中,它是直接订阅了上游的observable,在上面示例中也就是第二个observableObservableDoOnLifecyclesubscribeActual方法如下:

上一篇介绍了在每一个Observable的订阅方法中,会先创建装饰的observer,并且把下游的observer传到创建的装饰的observer中,接着会给下游的observer添加订阅的回调,接着会给上游的observable添加订阅,而在此处的ObservableDoOnLifecycle订阅方法中先是创建了DisposableLambdaObserver的装饰observer,接着给上游的observable添加订阅。那给下游的obserer添加订阅的监听呢,这就放在了DisposableLambdaObserver的装饰observer的onSubscribe中了。

由于上面我们通过doOnSubscribe生成最下游的observable(ObservableDoOnLifecycle)的订阅线程是io线程,所以它的上游observable也是io线程,我们还没分析完doOnSubscribe传进去的Consumer的accept方法发生的线程,这个需要我们看下上面分析的ObservableDoOnLifecycle订阅中创建的装饰DisposableLambdaObserver:

上面一上来就是给传进来的Consumer执行了accept的回调,紧接着给下游的observer添加订阅的监听,方便下游的observer能收到订阅的回调啊,是不是这么回事呢?

那此处装饰的observer(DisposableLambdaObserver)订阅监听是由谁发起的呢,肯定是上游的observable开始订阅的时候发起的下游observer订阅监听啊,而上面我们分析了此处的上游observable订阅线程是由紧挨doOnSubscribesubscribeOn决定的,所以此处不难看出最终doOnSubscribe中的consumer监听的是subscribeOn指定上游的observable订阅过程中发生的线程,大家可以多理解这句话!!!

下面画张图补补脑:

doOnSubscribe小节

上面分析在没有结合源码的情况下,不好分析,整体就是subscribeOn会指定它上游的observable线程,而它上游又正好是doOnSubscribe生成的observable,该observable是ObservableDoOnLifecycle,在它的订阅里面又直接去订阅了它上游的observable,所以此时doOnSubscribe的上游observable线程也是doOnSubscribe它下面的subscribeOn指定的,而doOnSubscribe的上游observable是subscribeOn生成的,它是ObservableSubscribeOn,在它的订阅里面是直接监听了下游的observer订阅回调,也就是doOnSubscribe生成的ObservableDoOnLifecycle订阅中生成的装饰DisposableLambdaObserver,它的订阅监听会调用doOnSubscribe传进来的Consumer的accept方法。

所以这就是多次调用subscribeOn可以通过doOnSubscribe来做线程切换的监听。

doOnNext监听observeOn线程的切换,map的apply方法的线程由谁控制?

首先我们还是通过例子来回答上面你的问题,先来看doOnNext的使用:

关于doOnNext其实很好理解,发射数据因为是从上游的observable到下游的observable,而observeOn是指定下游的observer发射数据的线程,这个我在上一篇讲过,而doOnNext实际生成的是一个ObservableDoOnEach的observable,在该订阅方法中,会生成装饰的observer,也就是DoOnEachObserver,所以observeOn实际是控制了DoOnEachObserver发射数据的线程,而在它发射onNext数据的时候,会调用onNext传进来的Consumer的accept方法:

observable里面还有onComplete、onError的监听,他们最终都是生成了ObservableDoOnEach的observable

doOnNext小节

doOnNext中是通过传进去的Consumer作为上游发射数据过来的监听,在上游observable发射数据的时候,会执行doOnNext的Consumer的accept方法,所以在上面多次通过observeOn指定线程的时候,可以通过doOnNext拿到切换线程的。

所以这就是多次调用observeOn可以通过onNext来做线程切换的监听。

关于map的apply方法的线程由谁来控制,我们这块直接看map的observable,它是一个ObservableMap

不难看出,map操作符生成的ObservableMap,在它的订阅方法中,生成装饰的MapObserver,接着给上游的observable添加订阅,在MapObserver接收到上游的observable发射onNext数据的时候会调用map传进来的function的apply方法,因此apply的方法是跟上游的observable发射数据的线程有关,我们来看下面例子:

我们知道subscribeOn是指定上游的observable的订阅线程,我们在上篇文章讲过多个subscribeOn指定线程,只有第一次有效,这是针对最上游的observable而言的,所以最上游的observable发射数据端的线程紧跟它后面指定的io线程保持一致,所以会有如下打印:

而在每一个subscribeOn发射数据的时候不会改变线程,所以map的线程会保持最上游的observable的线程,也就是io线程,所以打印会有如下:

既然subscribeOn不会改变发射数据的线程,导致多次subscribeOn不会改变map的线程,所以只会跟最上游的observable发射数据的线程保持一致,那我们如果中间插入observeOn呢,下面来看下这个例子:

由于observeOn会改变给下游发送数据的时候线程,也就是改变下游observer接收数据的线程,也即onNext、onComplete、onError方法,所以observeOn指定的线程会一直传到了下游MapObserver的onNext方法中,所以最终map中的function的apply方法是main线程,打印结果如下:

其他情况大家可以尝试,比如多次指定observeOn线程,看map最终的线程如何

map小节

map将传进去的function作为上游发射数据过来的监听,在上游observable发射数据的时候,会执行function的apply方法来达到转换数据的目的,所以map中function的apply方法是跟上游的observable发射数据的线程有关。

如果不指定observer的线程,也就是指设置subscribeOn,而不设置observeOn,那observer的线程是什么样的?

我感觉理解了整个订阅的过程,其实理解这个问题一点都不难,既然subscribeOn是指定上游的observable的线程,那么最终的上游observable发射数据时候的线程也会被紧挨着它的subscribeOn指定的线程有关啊,并且不设置observeOn指定下游的observer的线程,那么observer的线程是不是跟最上游observable发射数据的线程保持一致啊。

背压是什么,以及Flowable怎么能控制背压?

它是指由于上游的observable发射数据太快,下游observer接收数据跟不上来导致的一种现象。可以形象理解为水坝在存储水的时候为了保持水的平衡,给下游的水库放水,同时会接收上游的水流,如果上游的水流很大,那么水坝中的水位激增,而水坝给下游放水的能力有限,所以就会导致水坝中的水漫过水坝。

RxJava1.0背压

注:说到RxJava背压还得从RxJava1.0开始说起,这里分析的RxJava1.0版本源码是在1.3.8版本分析

在RxJava的1.0版本中Observable是支持背压的,只不过它是以异常的形式展示给用户,下面我们拿上游不断地发送数据的例子来模拟下:

Observable.unsafeCreate(new Observable.OnSubscribe() {

@Override

public void call(Subscriber<? super Integer> subscriber) {

int i = 0;

while (true) {

subscriber.onNext(i);

i++;

}

}

}).subscribe(new Observer() {

@Override

public void onCompleted() {

}

@Override

public void onError(Throwable e) {

Log.d(TAG, “onError:” + e.getMessage());

e.printStackTrace();

}

@Override

public void onNext(Integer integer) {

Log.d(TAG, “onNext:” + integer);

}

});

在我们日志中,没看到下游处理数据处理不过来的问题,也没出现异常信息啊,这不挺好的,没出现背压的情况,看官,别急啊,背压是发生在多线程中的问题,因为在单线程中,发送数据和接收数据都是在一个线程中,所以每次发送数据前得等到下游处理完数据才发送数据。

好吧,说完这么多,我们还是加上多线程吧,我们再来试试:

Observable.unsafeCreate(new Observable.OnSubscribe() {

@Override

public void call(Subscriber<? super Integer> subscriber) {

for (int i = 0; ; i++) {

subscriber.onNext(i);

}

}

}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() {

@Override

public void onCompleted() {

}

@Override

public void onError(Throwable e) {

Log.d(TAG, “onError:” + e.getMessage());

e.printStackTrace();

}

@Override

public void onNext(Integer integer) {

Log.d(TAG, “onNext:” + integer);

}

});

我们在上游发送数据的时候,做了个死循环,然后指定了上游是io线程,下游是main线程,结果还没接收到数据直接报异常了:

报的是MissingBackpressureException异常信息,其实在RxJava1.0中这就是支持背压的策略,直接通过异常的信息反馈给用户。在RxJava1.0中支持最大的发射数据是16个,也就是说发射大于或等于17个的时候就会出现异常,下面通过代码验证下:

数据能正常接收,如果我们把数据调整到17呢,是不是会发生异常呢:

看吧,不用我说啥吧,看来底层是限制了上游发送数据的个数,其实这个是RxJava1.0背压策略的一种机制,通过数量来控制,我们可以在这里找到定义的数量大小:

RxJava背压对android平台做了发送数据的限制,如果大于16个则直接抛MissingBackpressureException异常,底层通过上游Observable发送的数据放到队列中,而这里的16则是定义队列的容量,每次在往队列中放数据的时候会先获取下一个要放数据的索引,如果发现索引位置的数据不为空,则认为队列已经满了,那么满了就直接返回onError的信息。

比如我们在发送第17个数据的时候,在获取索引的时候是通过与对接容量16 进行相与得到索引,相与之后得到一个小于16的索引,发现相与之后得到的索引上还有数据,则发送第17个数据放进队列的时候失败,所以直接抛出onError的信息。核心源码在这里:

这个过程还是蛮清晰的,OperatorObserveOnobserveOn方法传给OnSubscribeLiftOperator对象,在OperatorObserveOn中会先初始化队列,并且队列的容量是16,接着在onNext接收上游发送过来的数据的时候,会判断队列的offer是否成功,如果不成功,则直接抛onError的错误,那什么时候offer会失败呢,得看当前发送过来的数据是否超过了队列的容量,如果超过则offer失败。

所以这就是RxJava1.0中背压策略,通过设置上游发送过来的数据的接收队列容量来达到背压。

RxJava2.0背压

注:RxJava运行版本是2.2.20

在RxJava2.0中不再在Observable支持背压,而是通过Flowable来代替了,也就是说Observable中不再通过异常的形式告诉用户了,也就是不抛MissingBackpressureException异常了,下面来看看RxJava2.0正常发送数据的问题:

Observable.create(new ObservableOnSubscribe() {

@Override

public void subscribe(@NonNull ObservableEmitter emitter) throws Exception {

for (int i = 0; ; i++) {

emitter.onNext(i);

}

}

}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() {

@Override

public void onSubscribe(@NonNull Disposable d) {

}

@Override

public void onNext(@NonNull Integer integer) {

Log.d(TAG, “onNext:” + integer);

}

@Override

public void onError(@NonNull Throwable e) {

}

@Override

public void onComplete() {

}

});

虽然说不抛MissingBackpressureException异常了,但是内存占用很糟糕啊,所以针对RxJava2.0的问题,我们有没有处理办法呢,大家会说RxJava2.0不是已经支持Flowable了吗,直接使用它啊,如果让我们自己来处理啊该怎么办呢,首先我们分析背压产生的原因是什么:

  • 上游发送的事件太快,下游处理不过来

  • 上游发送的事件太多,下游处理不过来

首先针对第一种我们可以让上游发送速度慢点,怎么慢点呢,让io线程每次发送的时候停留一会:

Observable.create(new ObservableOnSubscribe() {

@Override

public void subscribe(@NonNull ObservableEmitter emitter) throws Exception {

for (int i = 0; ; i++) {

emitter.onNext(i);

Thread.sleep(1000);

}

}

}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() {

@Override

public void onSubscribe(@NonNull Disposable d) {

}

@Override

public void onNext(@NonNull Integer integer) {

Log.d(TAG, “onNext:” + integer);

}

@Override

public void onError(@NonNull Throwable e) {

}

@Override

public void onComplete() {

}

});

针对第二种的话,我们可以让下游的observer少接收点数据:

Observable.create(new ObservableOnSubscribe() {

@Override

public void subscribe(ObservableEmitter emitter) throws Exception {

for (int i = 0; ; i++) { //无限循环发事件

emitter.onNext(i);

}

}

}).filter(new Predicate() {

@Override

public boolean test(Integer integer) throws Exception {

return integer % 100 == 0;

}

})

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Consumer() {

@Override

public void accept(Integer integer) throws Exception {

Log.d(TAG, “” + integer);

}

});

知道了背压产生的原因后,我们再回头看RxJava本身用Flowable来支持背压策略,而且它的策略比较丰富,下面来一一介绍,我们先从Flowable.create入手: [图片上传中…(image-d673f0-1609070033423-9)]

FlowableCreate支持两个参数,第一个不用说了吧,是上游发射数据的FlowableOnSubscribe,第二个参数是背压的几种策略:

public enum BackpressureStrategy {

MISSING,//如果流的速度无法保持同步,可能会抛出MissingBackpressureException或IllegalStateException。

ERROR,//会在下游跟不上速度时抛出MissingBackpressureException。

BUFFER,//上游不断的发出onNext请求,直到下游处理完,也就是和Observable一样了,缓存池无限大,最后直到程序崩溃

DROP,//会在下游跟不上速度时把onNext的值丢弃。

LATEST//会一直保留最新的onNext的值,直到被下游消费掉。

}

在知道了Flowable这多的策略时候,我们先来看看Flowable在单线程下是什么样的:

在策略为error情况下,并且没切换线程的时候,直接报io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests错误,该错误是告诉你没对下游的observer设置request的方法,这个是由于在单线程情况下,没默认给observer设置处理数据的能力,也即是个数,所以上游不知道下游的处理能力,直接抛error错误。

下面怎么设置下游处理能力呢:

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
b405f5914acd38b4.png)

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:
[外链图片转存中…(img-90T6kOXP-1715356748801)]

[外链图片转存中…(img-znillvnD-1715356748802)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值