响应式框架java_RXJava2响应式编程框架设计<三>---Rxjava2背压、生命周期

本文深入探讨了RxJava2中的背压问题,包括产生原因、影响及解决策略,通过实验展示了背压可能导致的内存溢出。文章还介绍了如何使用Flowable和BackpressureStrategy应对异步数据流的挑战。此外,文章提到了RxJava2的生命周期管理,通过实例展示了未正确管理导致的问题,并引入了RxLifecycle库作为解决方案,提供了添加生命周期管理的步骤和代码示例。
摘要由CSDN通过智能技术生成

在上一次https://www.cnblogs.com/webor2006/p/12348890.html中已经完成了对RxJava2的整个线程切换原理的详细剖析了,这次继续来学习它其它比较重要的知识点,手写RxJava2核心原理放到下一次。

RxJava2背压【Backpressure】:

背压这个概念在RxJava1.x中是木有的,它是在RxJava2.x才提出来的,而这个概念又是很重要的,因为不了解它很容易在实际工作中使用带定时发送消息的场景时出现背压造成的程序崩溃,所以接下来详细来解读一下它。

出现原因:

当上下游在不同的线程中,通过Observable发射,处理,响应数据流时,如果上游发射数据的速度快于下游接收处理数据的速度,这样对于那些没来得及处理的数据就会造成积压,这些数据既不会丢失,也不会被垃圾回收机制回收,而是存放在一个异步缓存池中,如果缓存池中的数据一直得不到处理,越积越多,最后就会造成内存溢出,这便是响应式编程中的背压(backpressure)问题。关于背压的学习推荐该博文:https://www.jianshu.com/p/ceb48ed8719d,如博主所描述的背景情况,这里跟着梳理一下:

先来看一下观察者 & 被观察者 之间存在2种订阅关系:同步 & 异步:

同步订阅

a8e4cef92e76267492ef7fa546572335.png

它的订阅流程如下:

20f06d725cbbb760da382527f5abd6fc.png

异步订阅

70477ad80599543929deabceb1ca401a.png

其中标红的描述是产生背压的主要原因,下面再来看一下它的整个订阅流程,被观察者发送的事件先存到缓存区中:

4bfdd42fc4438ece15e8adfac33aa916.png

然后观察者则从缓存区中来取事件进行一一处理,如下:

34fc8e813aebe0e1f289d14f253d1d12.png

由这订阅关系所阐述出来的问题:

对于同步关系而言貌似木有啥问题,因为上游事件的发送都得要下游处理完了才能进行,而对于异步的订阅关系由于缓存区的一个原因会有如下问题产生:

190442feeaccca59a34fc634a36a8c89.png

那有啥影响呢?处理不过来就慢慢处理呗,但是极端情况下会出现这样,如博主的说明:

553d6f4d299c041d68fd86636002e706.png

这就是为啥对于背压情况是一定得要去处理的,因为如果不了解它在实际工作中很容易就趟雷了。

背压现象演示:

接下来通过实验来演示一下背压现象,只有清楚了背压的一个问题之后,你才能够针对性的解决它,好下面的demo直接用博主举的,不过为了能重现OOM的异常我将其数据量加大了:

被观察者的发送事件速度 = 10ms / 个,每个数据大小为1m,可能有点极端,但是能比较好的重现背压的问题。

观察者的接收事件速度 = 4s / 个。

代码如下:

public class MainActivity extendsAppCompatActivity {

@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Observable.create(new ObservableOnSubscribe() {//1. 创建被观察者 & 生产事件

@Overridepublic void subscribe(ObservableEmitter emitter) throwsException {byte[] data = new byte[1024];for (int i = 0; ; i++) {

Log.d("cexo", "发送了事件" +i);

Thread.sleep(10);//发送事件速度:10ms / 个

emitter.onNext(newString(data));

}

}

}).subscribeOn(Schedulers.io())//设置被观察者在io线程中进行

.observeOn(AndroidSchedulers.mainThread()) //设置观察者在主线程中进行

.subscribe(new Observer() {//2. 通过通过订阅(subscribe)连接观察者和被观察者

@Overridepublic voidonSubscribe(Disposable d) {

Log.d("cexo", "开始采用subscribe连接");

}

@Overridepublic voidonNext(String value) {try{//接收事件速度:4s / 个

Thread.sleep(4000);

Log.d("cexo", "接收到了事件" +value);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

@Overridepublic voidonError(Throwable e) {

Log.d("cexo", "对Error事件作出响应");

}

@Overridepublic voidonComplete() {

Log.d("cexo", "对Complete事件作出响应");

}

});

}

}

这里在我现在在用的Redmi Note 7没能等到OOM的情况,可能是手机内存比较高,机身内存是6G的,做实验内存中间有回落的情况,但是总的内存是持续的往上增,能增到三四兆,所以OOM肯定是会有的,因为实际商用APP本身加载正常页面就占一定的内存;另外我换了个配置稍低一占的Redmi 3s,机身内存是3G的,用profile运行一段时间的结果为:

d83728a88271784c5ffb4ee402f4f1f5.png

到达198MB左右,应用崩掉了,看一下日志:

02-24 09:21:35.715 17990-17998/? I/art: Starting a blocking GC Alloc02-24 09:21:35.715 17990-18026/? W/art: Throwing OutOfMemoryError "Failed to allocate a 28 byte allocation with 0 free bytes and -2832B until OOM" (recursive case)02-24 09:21:35.715 17990-18024/? I/art: Waiting fora blocking GC Alloc02-24 09:21:35.741 17990-18026/? W/art: "RxCachedThreadScheduler-1" daemon prio=5 tid=14Runnable02-24 09:21:35.741 17990-18026/? W/art: | group="main" sCount=0 dsCount=0 obj=0x2ac0b520 self=0x559125c5b0

02-24 09:21:35.741 17990-18026/? W/art: | sysTid=18026 nice=0 cgrp=default sched=0/0 handle=0x7f980cc450

02-24 09:21:35.741 17990-18026/? W/art: | state=R schedstat=( 7951401546 970377205 16160 ) utm=607 stm=188 core=6 HZ=100

02-24 09:21:35.741 17990-18026/? W/art: | stack=0x7f97fca000-0x7f97fcc000 stackSize=1037KB02-24 09:21:35.741 17990-18026/? W/art: | held mutexes= "mutator lock"(shared held)02-24 09:21:35.741 17990-18026/? W/art: native: #00 pc 00000000004903bc /system/lib64/libart.so (_ZN3art15DumpNativeStackERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEEiP12BacktraceMapPKcPNS_9ArtMethodEPv+200)02-24 09:21:35.741 17990-18026/? W/art: native: #01 pc 000000000045a100 /system/lib64/libart.so (_ZNK3art6Thread9DumpStackERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEEP12BacktraceMap+220)02-24 09:21:35.741 17990-18026/? W/art: native: #02 pc 0000000000463b98 /system/lib64/libart.so (_ZN3art6Thread21ThrowOutOfMemoryErrorEPKc+244)02-24 09:21:35.741 17990-18026/? W/art: native: #03 pc 0000000000244e88 /system/lib64/libart.so (_ZN3art2gc4Heap21ThrowOutOfMemoryErrorEPNS_6ThreadEmNS0_13AllocatorTypeE+544)02-24 09:21:35.741 17990-18026/? W/art: native: #04 pc 000000000024b1ac /system/lib64/libart.so (_ZN3art2gc4Heap22AllocateInternalWithGcEPNS_6ThreadENS0_13AllocatorTypeEmPmS5_S5_PPNS_6mirror5ClassE+1244)02-24 09:21:35.741 17990-18026/? W/art: native: #05 pc 000000000017afd4 /system/lib64/libart.so (_ZN3art6mirror5Class11AllocObjectEPNS_6ThreadE+268)02-24 09:21:35.741 17990-18026/? W/art: native: #06 pc 000000000046314c /system/lib64/libart.so (_ZN3art6Thread24ThrowNewWrappedExceptionEPKcS2_+668)02-24 09:21:35.741 17990-18026/? W/art: native: #07 pc 0000000000463c40 /system/lib64/libart.so (_ZN3art6Thread21ThrowOutOfMemoryErrorEPKc+412)02-24 09:21:35.741 17990-18026/? W/art: native: #08 pc 0000000000244e88 /system/lib64/libart.so (_ZN3art2gc4Heap21ThrowOutOfMemoryErrorEPNS_6ThreadEmNS0_13AllocatorTypeE+544)02-24 09:21:35.741 17990-18026/? W/art: native: #09 pc 000000000024b1ac /system/lib64/libart.so (_ZN3art2gc4Heap22AllocateInternalWithGcEPNS_6ThreadENS0_13AllocatorTypeEmPmS5_S5_PPNS_6mirror5ClassE+1244)02-24 09:21:35.741 17990-18026/? W/art: native: #10 pc 00000000003b8144 /system/lib64/libart.so (_ZN3artL32StringFactory_newStringFromCharsEP7_JNIEnvP7_jclassiiP11_jcharArray+396)02-24 09:21:35.741 17990-18026/? W/art: native: #11 pc 0000000000362424 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_java_lang_StringFactory_newStringFromChars__II_3C+168)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.StringFactory.newStringFromChars!(Native method)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.StringFactory.newStringFromChars(StringFactory.java:218)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.StringFactory.newStringFromBytes(StringFactory.java:203)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.StringFactory.newStringFromBytes(StringFactory.java:53)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.StringFactory.newStringFromBytes(StringFactory.java:45)02-24 09:21:35.741 17990-18026/? W/art: at com.android.rxjavastudy.MainActivity$2.subscribe(MainActivity.java:32)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.Observable.subscribe(Observable.java:12051)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:579)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)02-24 09:21:35.741 17990-18026/? W/art: at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)02-24 09:21:35.741 17990-18026/? W/art: at java.util.concurrent.FutureTask.run(FutureTask.java:237)02-24 09:21:35.741 17990-18026/? W/art: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:154)02-24 09:21:35.741 17990-18026/? W/art: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)02-24 09:21:35.741 17990-18026/? W/art: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)02-24 09:21:35.741 17990-18026/? W/art: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)02-24 09:21:35.741 17990-18026/? W/art: at java.lang.Thread.run(Thread.java:818)02-24 09:21:35.742 17990-18026/? I/art: Waiting fora blocking GC Alloc02-24 09:21:35.750 17990-17998/? I/art: Alloc sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 192MB/192MB, paused 1.279ms total 34.765ms02-24 09:21:35.750 17990-18024/? I/art: WaitForGcToComplete blocked for 194.640ms for cause Alloc

确实是OOM了。。

解决之道:

来看博主对于这块的描述:在 异步订阅关系 中,控制事件发送 & 接收的速度,注:背压的作用域 = 异步订阅关系,即 被观察者 & 观察者处在不同线程中。

fbedfee8d09a137931cdfd9e789eb409.png

背压原理:

那在RxJava中实现背压策略的原理思想是这样的:

6842e55c9e56b0269ec6537314225810.png

对于上面的策略用下图来说明一下:

d199851f11cbe9e22635dc1360357ccf.png

背压策略的具体实现:Flowable

在RxJava2.0中,是采用Flowable来实现背压策略的,也叫是 “非阻塞式背压” 策略。下面用图来说明下它的特点:

012f3b2ba74fb0d643a5c509d0c6d477.png

具体使用:

采用带背压策略的代码:

Flowable.create(new FlowableOnSubscribe() {

@Override

public void subscribe(FlowableEmitter emitter) throwsException {

byte[] data = new byte[1024];

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

Log.d("cexo", "发送了事件" +i);

Thread.sleep(10);

// 发送事件速度:10ms / 个

emitter.onNext(newString(data));

}

}

}, BackpressureStrategy.LATEST).subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行

.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行

.subscribe(new FlowableSubscriber() {

@Override

public voidonSubscribe(Subscription s) {

Log.d("cexo", "开始采用subscribe连接");

s.request(Long.MAX_VALUE);

}

@Override

public voidonNext(String value) {

try{

// 接收事件速度:5s / 个

Thread.sleep(4000);

Log.d("cexo", "接收到了事件" +value);

} catch(InterruptedException e) {

e.printStackTrace();

}

}

@Override

public voidonError(Throwable e) {

Log.d("cexo", "对Error事件作出响应");

}

@Override

public voidonComplete() {

Log.d("cexo", "对Complete事件作出响应");

}

});

其中可以看到在创建Flowable这个被观察者时强制要求必须要指定一个背压策略BackpressureStrategy,它可以取如下值:

8e556aaa31c20883264d7adda55b32ee.png

咱们来简单看一下这块的源码:

1c8b05e9e203184e30fd5bbea412b8a0.png

9310503dc2aaedc5c92550c7fc16b62c.png

然后我们在订阅时则会根据我们传进去的背压策略进行相应的处理:

774bd49ae11c2cfc295be85f9335dcb8.png

关于它里面的具体流程这里暂且就不分析了,整体流程跟上一次分析的其实差不多,主要是对其原理有个了解,具体以后如果遇到了再进行研究,明白了大体原理其实再现学也比较轻松了,关于背压还有一个响应式拉取策略,也就是:

6236d52373368d5619f49653862d6b92.png

5e3368c565c9085d91be031c77ebaadd.png

响应式拉取方式,来设置下游对数据的请求数量,上游可以根据下游的需求量,按需发送数据,如果不显示调用request()则默认下游的需求量为零,所以运行上面的代码后,上游Flowable发射的数据不会交给下游Subscriber处理。下面来演示一下这个参数的含义:

b940c6c002e301394e180f62c447eab3.png

运行一下:

c48186422a1eac46a8cd69e95e7b3979.png

其中关于背压策略提到了缓存池,那它的大小是多少呢,可以定位源码瞅一下:

72b8391e726d4b095649ae2374bcfa32.png

好了关于背压这块的东东先了解到这,各个策略的具体效果可以参考大神的这篇博文https://www.jianshu.com/p/ceb48ed8719d,对我来说重点是知道背压出现的原因以及如果出现了能有解决之道就可以了。

RxJava2生命周期:

问题演示:

先不多说,看一下例子:

6b9bb1b87918deac52e59417a65fc7ac.png

运行看下效果:

f4335082354e2b567239fd95ef1bb35e.gif

看到木有,这是木有加上生命周期管理的问题,也就是在Activity退出之后,该RxJava的定时器还在跑,对于RxJava不是有一个Disposable可以用来取消么?确实是的,咱们来修改一下:

858deab0ef4570c4298c95c9c43de113.png

运行看一下:

b010fe0b1b2400df51d73a4c363a98e8.gif

这个很easy嘛,有啥有提的,这里是打算采用一个框架来对它的生命周期进行管理,其原理肯定也是用到了这个Disposable的,地址为:https://github.com/trello/RxLifecycle,用起来也比较方便,顺便来拓展一下见识。

加入生命周期管理:

先来看一下官网的介绍:

59de2cdb941dbd814e96f43f74a140c0.png

其使用方式官网也介绍了,比较简单:

1e3c57c5737fd06b5e96e39129703b73.png

好,下面咱们来使用一下,先添加依赖:

85cac711a68e9d8ca3cb166b65c282fc.png

这里有个注意点,就是这个库对于support包需要是androidx的,如果不是则编译会报错的,比如:

b0b6334735e40a09a83b8047a66ba08b.png

好,接下来咱们加入生命周期的代码:

47cf4413f3e41044356fdbcefe9509a0.png

此时再运行:

edde7824165cd7dbbc8f74cd564c8043.gif

接下来简单瞅一下它的代码,这里用到了一个compose()操作符:

913d4b79e3ca63d4650167d857beb39a.png

这个compose能够实现我们自己的操作符,关于如何实现可以参阅https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators,这里就不细研究了,然后它调用了bindUntilEvent()方法,它来自于:

77983e5f3dc3b7954e1a98a298d046c4.png

返回了一个LifecycleTransformer对像,瞅一眼:

9ec29d6f89b7f273f7a474627afe0659.png

实际使用时应该没有可能自已来手动定义一个操作符的,所以这里了解一下既可,再来看一下在这个Activity中生命周期中处里了生命周期的管理 :

1decafd710a8bdca23ac90ad32c9efcf.png

其中lifecycleSubject是Rxjava框架提供的类:

0d4c49010b5b0f366961d44170ae5eef.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值