RxJava的核心内容分析
Rxjava的设计模式
- 观察者模式
- 装饰者模式
Rxjava的核心内容
- Rxjava背压策略
- Rxjava线程切换
- Rxjava生命周期管理
Rxjava背压策略
什么是背压
当上下游在不同的线程中,通过Observable
发射,处理,响应数据流时,如果上游发射数据的速度快于下游接收处理的速度,导致没来得及处理的数据就会造成积压,这些数据既不会丢失,也不会被GC,而是存在一个异步缓存池中,如果缓存池的数据一直得不到处理,越积越多,最后就会造成内存OOM,这边是响应式编程的背压问题。使用白话来举例子:好比不停的往水池里面加水,水池也在出水,但是出水比进水慢的多,最后会导致水池爆满溢出。
背压策略的解决思路
通过响应式拉取,即观察者主动去被观察者那拉取事件,而被观察者发送的事件则加入异步缓存池等待被拉取,说白了就是一个被动接受,一个是主动拉取。
例子
无背压策略的情况:
如图所示,蓝色的框框就是zip
组合操作符给我们的‘水池’。它将每根水管发出的事件保存起来,等两个水池都有事件之后就分别从水池中取出一个事件来组合,当其中一个水池为空的时候就处于等待状态。
// 在RxJava2.0开始,Observable不再支持背压。
val observable1 = Observable.create<Int> { emitter ->
var index = 0;
while (true) { //无限发送事件,每100ms发送一个事件
Log.i(TAG, "observable2 emitter $index")
emitter.onNext(index)
Thread.sleep(100)
index++
}
}.subscribeOn(Schedulers.newThread())
val observable2 = Observable.create<String> { emitter ->
while (true) { //无限发送事件,每1000ms发送一个事件
Log.i(TAG, "observable2 emitter A")
emitter.onNext("A")
Thread.sleep(1000)
}
}.subscribeOn(Schedulers.newThread())
Observable.zip(observable1, observable2, object : BiFunction<Int, String, String> {
override fun apply(t1: Int, t2: String): String {
// observable1发送事件的速度大于observable2
// 当observable2发送事件时observable1才会立即先从“水池”中取出事件与其相组合
val content = t2.plus(t1)
Log.i(TAG, "zip apply:$content")
return content
}
}).subscribeOn(AndroidSchedulers.mainThread())
.subscribe(object : Consumer<String>{
override fun accept(s: String?) {
Log.i(TAG,"accept:$s")
}
},object :Consumer<Throwable>{
override fun accept(t: Throwable?) {
Log.i(TAG,"throwable:$t")
}
})
运行结果
2021-01-15 16:38:48.740 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 0
2021-01-15 16:38:48.740 12646-12688/com.example.rxjavatest I/lxl: observable2 emitter A
2021-01-15 16:38:48.740 12646-12688/com.example.rxjavatest I/lxl: zip apply:A0
2021-01-15 16:38:48.741 12646-12688/com.example.rxjavatest I/lxl: accept:A0
2021-01-15 16:38:48.841 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 1
2021-01-15 16:38:48.941 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 2
2021-01-15 16:38:49.041 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 3
2021-01-15 16:38:49.142 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 4
2021-01-15 16:38:49.242 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 5
2021-01-15 16:38:49.343 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 6
2021-01-15 16:38:49.443 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 7
2021-01-15 16:38:49.544 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 8
2021-01-15 16:38:49.644 12646-12687/com.example.rxjavatest I/lxl: observable1 emitter 9
2021-01-15 16:38:49.741 12646-12688/com.example.rxjavatest I/lxl: observable2 emitter A
2021-01-15 16:38:49.741 12646-12688/com.example.rxjavatest I/lxl: zip apply:A1
2021-01-15 16:38:49.741 12646-12688/com.example.rxjavatest I/lxl: accept:A1
代码运行结果解读:
只有observable2发送事件后,zip才会进行组合事件,并发射的下游,当observable2无事件的时候,是处于等待状态的。
observable1和observable2好比两根水管,分别处于不同的线程中发送事件,但是observable2发送事件的速度明显要小于observable1的速度。根据zip
组合操作符的特性(最终发送的事件数量会与源Observable中最少事件的数量保持一致(也就是必须配对才会发送事件出去)
。也就是说只有在observable2发送事件后,observable1才会立即从缓存的事件中取出事件与其组合,发送给下游接收。如果任由observable1发送事件而不及时处理,最终“水池”中存放的事件会越来越多,最终OOM。
类似这种,任何上游发送的事件下游来不及处理,都可能会导致这种情况的发生。
解决想法
- 控制上游事件的发送速度和数量
- 缓存事件池增加手动开关,由自动控制变为主动控制
RxJava提供的解决方案 - Flowable/Subscriber,响应式拉取来主动控制释放缓存池中的事件数量来达到平衡。
Flowable-Subscriber
Flowable.create(object : FlowableOnSubscribe<Int> {
override fun subscribe(emitter: FlowableEmitter<Int>) {
// 发送事件
for (index in 0..5) {
Log.i(TAG, "Flowable emitter $index")
emitter.onNext(index)
}
}
}, BackpressureStrategy.MISSING)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Subscriber<Int> {
override fun onSubscribe(s: Subscription?) {
Log.i(TAG, "Subscription")
s?.let {
mSubscription = it
}
}
override fun onNext(t: Int?) {
Log.i(TAG, "onNext:$t")
}
override fun onError(t: Throwable?) {
Log.i(TAG, "onError:$t")
}
override fun onComplete() {
Log.i(TAG, "onComplete")
}
})
看上面这段代码,我们可以发现Flowable用法其实与Observable/Observer基本一样,区别就是发射器ObservableEmitter替换成了FlowableEmitter,Disposable替换成了Subscription,增加了一个参数背压模式BackpressureStrategy.MISSING (共5种后面会提到)。
运行结果
2021-01-15 16:48:23.200 14463-14463/com.example.rxjavatest I/lxl: onSubscribe
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 0
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 1
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 2
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 3
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 4
2021-01-15 16:48:23.201 14463-14515/com.example.rxjavatest I/lxl: Flowable emitter 5
看打印结果可知,我们只看到了上游发送的日志,下游没有接收到?是因为上游发送的事件会被保存缓存中“水池”,等待下游接收。
//控制开关“放水”:由自己手动控制是否接收事件
//int参数指定取出事件数量
mSubscription?.request(1)
运行结果
2021-01-15 16:57:52.063 15501-15501/com.example.rxjavatest I/lxl: onSubscribe
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 0
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 1
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 2
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 3
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 4
2021-01-15 16:57:52.065 15501-15541/com.example.rxjavatest I/lxl: Flowable emitter 5
2021-01-15 16:57:52.083 15501-15501/com.example.rxjavatest I/lxl: onNext:0
2021-01-15 16:57:52.083 15501-15501/com.example.rxjavatest I/lxl: onNext:1
通过Subscription.request(n)方法拉取数据,当所有事件被取出后才会调用onComplete()。注意onComplete() 和 OnError()不占用事件个数。
Observable和Flowable两种模式的数据传输的区别:
- Observable/Oberver,上游发送数据放到缓存区,下游自动从缓存获取数据,中间没有开关控制,如果上游发送数据的速率超过了下游接收处理数据的速率,那么缓存区的数据就会不断增大 (缓存大小默认最大缓存),直到OOM。
- Flowable/Subscriber,上游发送数据放到缓存区,下游不会自动从缓存区获取数据,中间有开关控制。Subscription.request(n),控制接收数据的个数。如果上游一直发送事件,而下游不接受,那么缓存区会怎么样呢?这就是下面我们要讲的了 - 背压策略。
背压策略
public abstract class Flowable<T> implements Publisher<T> {
/** The default buffer size. */
static final int BUFFER_SIZE;
static {
BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));
}
从Flowable的源码中我们可以找到,默认缓存大小为128
。
BackpressureStrategy,共有5种模式:
- MISSING 写入OnNext事件时不会进行任何缓冲或丢弃,下游必须处理任何溢出。当超过缓存区大小的时候,会自动发送一个onError事件 (onError和onComplete不占用事件数),即
MissingBackpressureException: Queue is full?! 。
- ERROR和MISSING相似 (在使用效果上),也会自动发送一个onError事件,不同的是错误信息,
MissingBackpressureException: create: could not emit value due to lack of requests 。
- BUFFER缓存区大小没有限制,也就是上游发送的事件在下游没有接收,会一直在缓存区里,直到缓存溢出即OOM (
跟Observable不同的是Flowable是响应式拉取
)。 - DROP当超过缓存区大小的时候,后续发送的事件就会被丢弃。
直到缓存区有事件被取出,新发送的事件才能继续加入到缓存池中。
- LATEST和DROP类似,当超过缓存区大小的时候,后续发送的事件就会被丢弃。区别在于超出缓存区大小后,会强行加入一条最新事件 (这个事件不占用缓存池大小),并且会不断被新的事件所覆盖。
确保了在调用onComplete()之前,一定能获取到最后发送的事件。
Flowable使用操作符Flowable中有些创建操作符不能指定背压策略模式时:
操作符 | 对应的背压策略 |
---|---|
onBackpressureBuffer() | BackpressureStrategy.BUFFER |
onBackpressureDrop() | BackpressureStrategy.DROP |
onBackpressureLatest() | BackpressureStrategy.LATEST |
Rxjava线程切换
Rxjava线程切换主要是使用这2个操作符:
- subscribeOn
通过接收一个Schedule参数,来指定对数据的处理运行在特定的线程调度器Schedule上,如多次使用则只有一次起作用。
使用subscribeOn操作符指定被观察者发送事件的线程
observable.subscribeOn(Schedulers.io())
- observeOn
接收一个Scheduler参数,来指定下游操作运行在特定的线程调度器Scheduler上。若多次设定,每次均起作用。
使用observeOn操作符指定观察者处理事件线程:
observable.observeOn(AndroidSchedulers.mainThread())
在每次都需要切换线程的时候,都需要写以上代码,所以我们可以使用compose
操作符。
ObservableTransformer<Upstream, Downstream> , 是对上游Observable转换成新的Observable的接口,需自己实现apply()方法:
fun <T> applySchedulers() = ObservableTransformer<T, T> {
//ObservableTransformer操作符不同于Function
// 它支持的是对原始的Observable进行操作,可转换成新的Observable
// 配合compose操作符来实现一些公用的逻辑处理
it.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
使用compose操作符加ObservableTransformer来复用线程切换操作:
Observable.create<Int> { emitter ->
// 2.在复写的subscribe()里定义需要发送的事件
emitter.onNext(1)
emitter.onNext(2)
emitter.onNext(3)
// 3.发送事件完成。
emitter.onComplete()
}.flatMap(object : Function<Int, Observable<String>> {
override fun apply(t: Int): Observable<String> {
return Observable.just("flatMap:$t")
}
}).compose(applySchedulers())
.subscribe(object : Observer<String> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "开始采用subscribe连接")
}
override fun onNext(t: String) {
Log.d(TAG, "对Next事件" + t + "作出响应")
}
override fun onError(e: Throwable) {
Log.d(TAG, "对Error事件作出响应")
}
override fun onComplete() {
Log.d(TAG, "对Complete事件作出响应")
}
})
Schedulers
类:
public final class Schedulers {
// 此款调度器非常简单,由一个线程支持。所以无论有多少个observables,都将只运行在这个线程上。
// 也可将其认为是主线程的一个替代。
@NonNull
static final Scheduler SINGLE;
// 这个调度器和IO调度器非常相似,因其也是由线程池支持的。
// 可用的线程数是固定的,和系统的cpu核数目保持一致。
// 所以如果你的手机是双核的,那么线程池中就有两个线程。这也意味着如果这两条线程都处于忙碌状态,
// 那么该进程将会等待线程变成空闲状态的时候才能处理其他操作。因为这个限制,它不太适合IO相关操作。
// 适用于进行一些计算操作,计算速度还很快。
@NonNull
static final Scheduler COMPUTATION;
// 最常见的调度器之一。它们用于IO相关操作。比如网络请求和文件操作。IO调度器背后由线程池支撑。
// 它首先创建一个工作线程,可以复用于其他操作。
// 当然,当这个工作线程(长时间任务的情况)不能被复用时,会创建一个新的线程来处理其他操作。
// 这个好处也会带来一些问题,因为允许创建的线程是无限的,所以当创建大量线程的时候,会对整体性能造成一些影响。
@NonNull
static final Scheduler IO;
// 此调度器运行在当前线程,所以如果你有代码运行在主线程上,它会将将要运行的代码块添加到主线程的队列。
// Trampoline不会阻塞此线程,它会等待当前任务执行完成。
// 适用于不止一个observables,并且希望他们能够按照顺序执行的场景。
@NonNull
static final Scheduler TRAMPOLINE;
// 此调度器会在当前线程开启一个新的子线程,相当于newThread()操作。
@NonNull
static final Scheduler NEW_THREAD;
...
}
AndroidSchedulers
类:
由RxAndroid库提供,使用AndroidSchedulers.mainThread()
获取一个持有主线程Looper的Handler的HandlerScheduler。
常用于将操作切换到主线程以便操作UI,经常用于observeOn方法。
public final class AndroidSchedulers {
private static final class MainHolder {
// 创建持有主线程Looper的Handler,传入给HandlerScheduler
static final Scheduler DEFAULT = new HandlerScheduler(new Handler(Looper.getMainLooper()), false);
}
private static final Scheduler MAIN_THREAD = RxAndroidPlugins.initMainThreadScheduler(
new Callable<Scheduler>() {
@Override public Scheduler call() throws Exception {
return MainHolder.DEFAULT;
}
});
/** A {@link Scheduler} which executes actions on the Android main thread. */
public static Scheduler mainThread() {
return RxAndroidPlugins.onMainThreadScheduler(MAIN_THREAD);
}
......
}
Rxjava线程切换原理
具体的讲解见深入浅出聊RxJava合集(Kotlin版)第三篇的Rxjava原理剖析。
Rxjava生命周期管理
Rxjava是一个异步的库,很容易就会造成内存泄漏,这样就需要进行生命周期的管理,我们的做法是:
Observable.interval(0, 1, TimeUnit.SECONDS)
.flatMap(object : Function<Long, Observable<String>> {
override fun apply(t: Long): Observable<String> {
return Observable.just("flatMap:$t")
}
}).compose(applySchedulers())
.subscribe(object : Observer<String> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "开始采用subscribe连接")
disposable = d
}
override fun onNext(t: String) {
Log.d(TAG, "对Next事件" + t + "作出响应")
}
override fun onError(e: Throwable) {
Log.d(TAG, "对Error事件作出响应")
}
override fun onComplete() {
Log.d(TAG, "对Complete事件作出响应")
}
})
override fun onDestroy() {
super.onDestroy()
// 在Activity退出的时候解除订阅,停止发送!
disposable?.dispose()
}
2021-01-16 16:06:31.106 31885-31885/com.example.rxjavatest D/lxl: 开始采用subscribe连接
2021-01-16 16:06:31.126 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:0作出响应
2021-01-16 16:06:32.109 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:1作出响应
2021-01-16 16:06:33.111 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:2作出响应
2021-01-16 16:06:34.111 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:3作出响应
2021-01-16 16:06:35.112 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:4作出响应
2021-01-16 16:06:36.110 31885-31885/com.example.rxjavatest D/lxl: 对Next事件flatMap:5作出响应
2021-01-16 16:06:36.711 31885-31885/com.example.rxjavatest D/lxl: 对Complete事件作出响应
退出Activity后,取消订阅,会停止发送和接收。
这样的话,每次都要进行这样写吗?不,肯定有替代的,这样我们就引入了RxLife
,下面是RxLife的使用。
在subscribe
之前加入以下代码。
.compose(bindUntilEvent(ActivityEvent.DESTROY))
// ActivityEvent.DESTROY 表示activity销毁的时候,对应activity的 onDestroy() 什么周期
/**
public enum ActivityEvent {
CREATE, // 对应 onCreate()
START, // 对应 onStart()
RESUME, // 对应 onResume()
PAUSE, // 对应 onPause()
STOP, // 对应 onStop()
DESTROY // 对应 onDestroy()
}
*/