前言
Rxjava
,由于其基于事件流的鏈式調用、邏輯簡潔 & 使用簡單的特點,深受各大 Android
開發者的歡迎。
如果還不了解RxJava,請看文章:Android:這是一篇 清晰 & 易懂的Rxjava 入門教程
- 本文主要講解的是
RxJava
中的 背壓控制策略,希望你們會喜歡。
Carson帶你學RxJava系列文章,包括 原理、操作符、應用場景、背壓等等,請關注看文章:Android:這是一份全面 & 詳細的RxJava學習指南
目錄
1. 引言
1.1 背景
- 觀察者 & 被觀察者 之間存在2種訂閱關系:同步 & 異步。具體如下:
- 對于異步訂閱關系,存在 被觀察者發送事件速度 與觀察者接收事件速度 不匹配的情況
- 發送 & 接收事件速度 = 單位時間內 發送&接收事件的數量
- 大多數情況,主要是 被觀察者發送事件速度 > 觀察者接收事件速度
1.2 問題
- 被觀察者 發送事件速度太快,而觀察者 來不及接收所有事件,從而導致觀察者無法及時響應 / 處理所有發送過來事件的問題,最終導致緩存區溢出、事件丟失 & OOM
- 如,點擊按鈕事件:連續過快的點擊按鈕10次,則只會造成點擊2次的效果;
- 解釋:因為點擊速度太快了,所以按鈕來不及響應
下面再舉個例子:
- 被觀察者的發送事件速度 = 10ms / 個
- 觀察者的接收事件速度 = 5s / 個
即出現發送 & 接收事件嚴重不匹配的問題
Observable.create(new ObservableOnSubscribe<Integer>() {
// 1. 創建被觀察者 & 生產事件
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
for (int i = 0; ; i++) {
Log.d(TAG, "發送了事件"+ i );
Thread.sleep(10);
// 發送事件速度:10ms / 個
emitter.onNext(i);
}
}
}).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Observer<Integer>() {
// 2. 通過通過訂閱(subscribe)連接觀察者和被觀察者
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "開始采用subscribe連接");
}
@Override
public void onNext(Integer value) {
try {
// 接收事件速度:5s / 個
Thread.sleep(5000);
Log.d(TAG, "接收到了事件"+ value );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "對Error事件作出響應");
}
@Override
public void onComplete() {
Log.d(TAG, "對Complete事件作出響應");
}
});
- 結果
由于被觀察者發送事件速度 > 觀察者接收事件速度,所以出現流速不匹配問題,從而導致OOM
示意圖
1.3 解決方案
采用 背壓策略。
下面,我將開始介紹背壓策略。
2. 背壓策略簡介
2.1 定義
一種 控制事件流速 的策略
2.2 作用
在 異步訂閱關系 中,控制事件發送 & 接收的速度
注:背壓的作用域 = 異步訂閱關系,即 被觀察者 & 觀察者處在不同線程中
2.3 解決的問題
解決了 因被觀察者發送事件速度 與 觀察者接收事件速度 不匹配(一般是前者 快于 后者),從而導致觀察者無法及時響應 / 處理所有 被觀察者發送事件 的問題
2.4 應用場景
- 被觀察者發送事件速度 與 觀察者接收事件速度 不匹配的場景
- 具體場景就取決于 該事件的類型,如:網絡請求,那么具體場景:有很多網絡請求需要執行,但執行者的執行速度沒那么快,此時就需要使用背壓策略來進行控制。
3. 背壓策略的原理
- 那么,RxJava實現背壓策略(
Backpressure
)的原理是什么呢? - 解決方案 & 思想主要如下:
- 示意圖如下
- 與
RxJava1.0
中被觀察者的舊實現Observable
對比
- 好了,那么上圖中在
RxJava 2.0
觀察者模型中,Flowable
到底是什么呢?它其實是RxJava 2.0
中被觀察者的一種新實現,同時也是背壓策略實現的承載者 - 請繼續看下一節的介紹:背壓策略的具體實現 -
Flowable
4. 背壓策略的具體實現:Flowable
在 RxJava2.0
中,采用 Flowable
實現 背壓策略
正確來說,應該是 “非阻塞式背壓” 策略
4.1 Flowable 介紹
- 定義:在
RxJava2.0
中,被觀察者(Observable
)的一種新實現
同時,
RxJava1.0
中被觀察者(Observable
)的舊實現:Observable
依然保留
- 作用:實現 非阻塞式背壓 策略
4.2 Flowable 特點
-
Flowable
的特點 具體如下
- 下面再貼出一張
RxJava2.0
與RxJava1.0
的觀察者模型的對比圖
實際上,
RxJava2.0
也有保留(被觀察者)Observerble - Observer(觀察者)的觀察者模型,此處只是為了做出對比讓讀者了解
4.3 與 RxJava1.0 中被觀察者的舊實現 Observable 的關系
- 具體如下圖
- 那么,為什么要采用新實現
Flowable
實現背壓,而不采用舊的Observable
呢? - 主要原因:舊實現
Observable
無法很好解決背壓問題。
4.4 Flowable的基礎使用
-
Flowable
的基礎使用非常類似于Observable
- 具體如下
/**
* 步驟1:創建被觀察者 = Flowable
*/
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onComplete();
}
}, BackpressureStrategy.ERROR);
// 需要傳入背壓參數BackpressureStrategy,下面會詳細講解
/**
* 步驟2:創建觀察者 = Subscriber
*/
Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
// 對比Observer傳入的Disposable參數,Subscriber此處傳入的參數 = Subscription
// 相同點:Subscription具備Disposable參數的作用,即Disposable.dispose()切斷連接, 同樣的調用Subscription.cancel()切斷連接
// 不同點:Subscription增加了void request(long n)
Log.d(TAG, "onSubscribe");
s.request(Long.MAX_VALUE);
// 關于request()下面會繼續詳細說明
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
};
/**
* 步驟3:建立訂閱關系
*/
upstream.subscribe(downstream);
- 更加優雅的鏈式調用
// 步驟1:創建被觀察者 = Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "發送事件 1");
emitter.onNext(1);
Log.d(TAG, "發送事件 2");
emitter.onNext(2);
Log.d(TAG, "發送事件 3");
emitter.onNext(3);
Log.d(TAG, "發送完成");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
// 步驟2:創建觀察者 = Subscriber & 建立訂閱關系
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(3);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 至此,
Flowable
的基礎使用講解完 - 關于更深層次的使用會結合 背壓策略的實現 來講解
5. 背壓策略的使用
- 在本節中,我將結合 背壓策略的原理 & Flowable的使用,為大家介紹在RxJava 2.0 中該如何使用Flowable來實現背壓策略功能,即背壓策略的使用
-
Flowable
與Observable
在功能上的區別主要是 多了背壓的功能 - 下面,我將順著第3節中講解背壓策略實現原理 & 解決方案(如下圖),來講解
Flowable
在背壓策略功能上的使用
注:
- 由于第2節中提到,使用背壓的場景 = 異步訂閱關系,所以下文中講解的主要是異步訂閱關系場景,即 被觀察者 & 觀察者 工作在不同線程中
- 但由于在同步訂閱關系的場景也可能出現流速不匹配的問題,所以在講解異步情況后,會稍微講解一下同步情況,以方便對比
5.1 控制 觀察者接收事件 的速度
5.1.1 異步訂閱情況
- 簡介
- 具體原理圖
- 具體使用
// 1. 創建被觀察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 一共發送4個事件
Log.d(TAG, "發送事件 1");
emitter.onNext(1);
Log.d(TAG, "發送事件 2");
emitter.onNext(2);
Log.d(TAG, "發送事件 3");
emitter.onNext(3);
Log.d(TAG, "發送事件 4");
emitter.onNext(4);
Log.d(TAG, "發送完成");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
// 對比Observer傳入的Disposable參數,Subscriber此處傳入的參數 = Subscription
// 相同點:Subscription參數具備Disposable參數的作用,即Disposable.dispose()切斷連接, 同樣的調用Subscription.cancel()切斷連接
// 不同點:Subscription增加了void request(long n)
s.request(3);
// 作用:決定觀察者能夠接收多少個事件
// 如設置了s.request(3),這就說明觀察者能夠接收3個事件(多出的事件存放在緩存區)
// 官方默認推薦使用Long.MAX_VALUE,即s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 效果圖
- 有2個結論是需要大家注意的
下圖 = 當緩存區存滿時(128個事件)溢出報錯的原理圖
- 代碼演示1:觀察者不接收事件的情況下,被觀察者繼續發送事件 & 存放到緩存區;再按需取出
/**
* 步驟1:設置變量
*/
private static final String TAG = "Rxjava";
private Button btn; // 該按鈕用于調用Subscription.request(long n )
private Subscription mSubscription; // 用于保存Subscription對象
/**
* 步驟2:設置點擊事件 = 調用Subscription.request(long n )
*/
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(2);
}
});
/**
* 步驟3:異步調用
*/
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "發送事件 1");
emitter.onNext(1);
Log.d(TAG, "發送事件 2");
emitter.onNext(2);
Log.d(TAG, "發送事件 3");
emitter.onNext(3);
Log.d(TAG, "發送事件 4");
emitter.onNext(4);
Log.d(TAG, "發送完成");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 保存Subscription對象,等待點擊按鈕時(調用request(2))觀察者再接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 代碼演示2:觀察者不接收事件的情況下,被觀察者繼續發送事件至超出緩存區大小(128)
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 一共發送129個事件,即超出了緩存區的大小
for (int i = 0;i< 129; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默認不設置可接收事件大小
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
5.1.2 同步訂閱情況
同步訂閱 & 異步訂閱 的區別在于:
- 同步訂閱中,被觀察者 & 觀察者工作于同1線程
- 同步訂閱關系中沒有緩存區
- 被觀察者在發送1個事件后,必須等待觀察者接收后,才能繼續發下1個事件
/**
* 步驟1:創建被觀察者 = Flowable
*/
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 發送3個事件
Log.d(TAG, "發送了事件1");
emitter.onNext(1);
Log.d(TAG, "發送了事件2");
emitter.onNext(2);
Log.d(TAG, "發送了事件3");
emitter.onNext(3);
emitter.onComplete();
}
}, BackpressureStrategy.ERROR);
/**
* 步驟2:創建觀察者 = Subscriber
*/
Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(3);
// 每次可接收事件 = 3 二次匹配
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件 " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
};
/**
* 步驟3:建立訂閱關系
*/
upstream.subscribe(downstream);
所以,實際上并不會出現被觀察者發送事件速度 > 觀察者接收事件速度的情況。可是,卻會出現被觀察者發送事件數量 > 觀察者接收事件數量的問題。
- 如:觀察者只能接受3個事件,但被觀察者卻發送了4個事件,所以出現了不匹配情況
/**
* 步驟1:創建被觀察者 = Flowable
*/
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 被觀察者發送事件數量 = 4個
Log.d(TAG, "發送了事件1");
emitter.onNext(1);
Log.d(TAG, "發送了事件2");
emitter.onNext(2);
Log.d(TAG, "發送了事件3");
emitter.onNext(3);
Log.d(TAG, "發送了事件4");
emitter.onNext(4);
emitter.onComplete();
}
}, BackpressureStrategy.ERROR);
/**
* 步驟2:創建觀察者 = Subscriber
*/
Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(3);
// 觀察者接收事件 = 3個 ,即不匹配
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件 " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
};
/**
* 步驟3:建立訂閱關系
*/
upstream.subscribe(downstream);
所以,對于沒有緩存區概念的同步訂閱關系來說,單純采用控制觀察者的接收事件數量(響應式拉取)實際上就等于 “單相思”,雖然觀察者控制了要接收3個事件,但假設被觀察者需要發送4個事件,還是會出現問題。
在下面講解 5.2 控制被觀察者發送事件速度 時會解決這個問題。
- 有1個特殊情況需要注意
- 代碼演示
/**
* 同步情況
*/
/**
* 步驟1:創建被觀察者 = Flowable
*/
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "發送了事件1");
emitter.onNext(1);
Log.d(TAG, "發送了事件2");
emitter.onNext(2);
Log.d(TAG, "發送了事件3");
emitter.onNext(3);
emitter.onComplete();
}
}, BackpressureStrategy.ERROR);
/**
* 步驟2:創建觀察者 = Subscriber
*/
Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 不設置request(long n)
// s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
};
/**
* 步驟3:建立訂閱關系
*/
upstream.subscribe(downstream);
在被觀察者發送第1個事件后, 就拋出MissingBackpressureException
異常 & 觀察者沒有收到任何事件
5.2 控制 被觀察者發送事件 的速度
- 簡介
-
FlowableEmitter
類的requested()
介紹
public interface FlowableEmitter<T> extends Emitter<T> {
// FlowableEmitter = 1個接口,繼承自Emitter
// Emitter接口方法包括:onNext(),onComplete() & onError
long requested();
// 作用:返回當前線程中request(a)中的a值
// 該request(a)則是措施1中講解的方法,作用 = 設置
....// 僅貼出關鍵代碼
}
每個線程中的
requested()
的返回值 = 該線程中的request(a)
的a值對應于同步 & 異步訂閱情況 的原理圖
為了方便大家理解該策略中的requested()
使用,該節會先講解同步訂閱情況,再講解異步訂閱情況
5.2.1 同步訂閱情況
- 原理說明
即在同步訂閱情況中,被觀察者 通過 FlowableEmitter.requested()
獲得了觀察者自身接收事件能力,從而根據該信息控制事件發送速度,從而達到了觀察者反向控制被觀察者的效果
- 具體使用
下面的例子 = 被觀察者根據觀察者自身接收事件能力(10個事件),從而僅發送10個事件
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 調用emitter.requested()獲取當前觀察者需要接收的事件數量
long n = emitter.requested();
Log.d(TAG, "觀察者可接收事件" + n);
// 根據emitter.requested()的值,即當前觀察者需要接收的事件數量來發送事件
for (int i = 0; i < n; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
}
}, BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 設置觀察者每次能接受10個事件
s.request(10);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 特別注意
在同步訂閱情況中使用FlowableEmitter.requested()
時,有以下幾種使用特性需要注意的:
情況1:可疊加性
- 即:觀察者可連續要求接收事件,被觀察者會進行疊加并一起發送
Subscription.request(a1);
Subscription.request(a2);
FlowableEmitter.requested()的返回值 = a1 + a2
- 代碼演示
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 調用emitter.requested()獲取當前觀察者需要接收的事件數量
Log.d(TAG, "觀察者可接收事件" + emitter.requested());
}
}, BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(10); // 第1次設置觀察者每次能接受10個事件
s.request(20); // 第2次設置觀察者每次能接受20個事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
情況2:實時更新性
- 即,每次發送事件后,emitter.requested()會實時更新觀察者能接受的事件
- 即一開始觀察者要接收10個事件,發送了1個后,會實時更新為9個
- 僅計算
Next
事件,complete & error
事件不算。
Subscription.request(10);
// FlowableEmitter.requested()的返回值 = 10
FlowableEmitter.onNext(1); // 發送了1個事件
// FlowableEmitter.requested()的返回值 = 9
- 代碼演示
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 1. 調用emitter.requested()獲取當前觀察者需要接收的事件數量
Log.d(TAG, "觀察者可接收事件數量 = " + emitter.requested());
// 2. 每次發送事件后,emitter.requested()會實時更新觀察者能接受的事件
// 即一開始觀察者要接收10個事件,發送了1個后,會實時更新為9個
Log.d(TAG, "發送了事件 1");
emitter.onNext(1);
Log.d(TAG, "發送了事件1后, 還需要發送事件數量 = " + emitter.requested());
Log.d(TAG, "發送了事件 2");
emitter.onNext(2);
Log.d(TAG, "發送事件2后, 還需要發送事件數量 = " + emitter.requested());
Log.d(TAG, "發送了事件 3");
emitter.onNext(3);
Log.d(TAG, "發送事件3后, 還需要發送事件數量 = " + emitter.requested());
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(10); // 設置觀察者每次能接受10個事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
情況3:異常
- 當
FlowableEmitter.requested()
減到0時,則代表觀察者已經不可接收事件 - 此時被觀察者若繼續發送事件,則會拋出
MissingBackpressureException
異常
如觀察者可接收事件數量 = 1,當被觀察者發送第2個事件時,就會拋出異常
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 1. 調用emitter.requested()獲取當前觀察者需要接收的事件數量
Log.d(TAG, "觀察者可接收事件數量 = " + emitter.requested());
// 2. 每次發送事件后,emitter.requested()會實時更新觀察者能接受的事件
// 即一開始觀察者要接收10個事件,發送了1個后,會實時更新為9個
Log.d(TAG, "發送了事件 1");
emitter.onNext(1);
Log.d(TAG, "發送了事件1后, 還需要發送事件數量 = " + emitter.requested());
Log.d(TAG, "發送了事件 2");
emitter.onNext(2);
Log.d(TAG, "發送事件2后, 還需要發送事件數量 = " + emitter.requested());
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(1); // 設置觀察者每次能接受1個事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
額外
- 若觀察者沒有設置可接收事件數量,即無調用
Subscription.request()
- 那么被觀察者默認觀察者可接收事件數量 = 0,即
FlowableEmitter.requested()
的返回值 = 0
5.2.2 異步訂閱情況
- 原理說明
從上面可以看出,由于二者處于不同線程,所以被觀察者 無法通過 FlowableEmitter.requested()
知道觀察者自身接收事件能力,即 被觀察者不能根據 觀察者自身接收事件的能力 控制發送事件的速度。具體請看下面例子
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 調用emitter.requested()獲取當前觀察者需要接收的事件數量
Log.d(TAG, "觀察者可接收事件數量 = " + emitter.requested());
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(150);
// 該設置僅影響觀察者線程中的requested,卻不會影響的被觀察者中的FlowableEmitter.requested()的返回值
// 因為FlowableEmitter.requested()的返回值 取決于RxJava內部調用request(n),而該內部調用會在一開始就調用request(128)
// 為什么是調用request(128)下面再講解
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
而在異步訂閱關系中,反向控制的原理是:通過RxJava
內部固定調用被觀察者線程中的request(n)
從而 反向控制被觀察者的發送事件速度
那么該什么時候調用被觀察者線程中的request(n)
& n
的值該是多少呢?請繼續往下看。
- 具體使用
關于RxJava
內部調用request(n)(n = 128、96、0)
的邏輯如下:
至于為什么是調用
request(128)
&request(96)
&request(0)
,感興趣的讀者可自己閱讀Flowable
的源碼
- 代碼演示
下面我將用一個例子來演示該原理的邏輯
// 被觀察者:一共需要發送500個事件,但真正開始發送事件的前提 = FlowableEmitter.requested()返回值 ≠ 0
// 觀察者:每次接收事件數量 = 48(點擊按鈕)
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "觀察者可接收事件數量 = " + emitter.requested());
boolean flag; //設置標記位控制
// 被觀察者一共需要發送500個事件
for (int i = 0; i < 500; i++) {
flag = false;
// 若requested() == 0則不發送
while (emitter.requested() == 0) {
if (!flag) {
Log.d(TAG, "不再發送");
flag = true;
}
}
// requested() ≠ 0 才發送
Log.d(TAG, "發送了事件" + i + ",觀察者可接收事件數量 = " + emitter.requested());
emitter.onNext(i);
}
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 初始狀態 = 不接收事件;通過點擊按鈕接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
// 點擊按鈕才會接收事件 = 48 / 次
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(48);
// 點擊按鈕 則 接收48個事件
}
});
整個流程 & 測試結果 請看下圖
5.3 采用背壓策略模式:BackpressureStrategy
5.3.1 背壓模式介紹
在Flowable的使用中,會被要求傳入背壓模式參數
- 面向對象:針對緩存區
- 作用:當緩存區大小存滿、被觀察者仍然繼續發送下1個事件時,該如何處理的策略方式
緩存區大小存滿、溢出 = 發送事件速度 > 接收事件速度 的結果 = 發送 & 接收事件不匹配的結果
5.3.2 背壓模式類型
下面我將對每種模式逐一說明。
模式1:BackpressureStrategy.ERROR
- 問題:發送事件速度 > 接收事件 速度,即流速不匹配
具體表現:出現當緩存區大小存滿(默認緩存區大小 = 128)、被觀察者仍然繼續發送下1個事件時
- 處理方式:直接拋出異常
MissingBackpressureException
// 創建被觀察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 發送 129個事件
for (int i = 0;i< 129; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.ERROR) // 設置背壓模式 = BackpressureStrategy.ERROR
.subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
模式2:BackpressureStrategy.MISSING
- 問題:發送事件速度 > 接收事件 速度,即流速不匹配
具體表現是:出現當緩存區大小存滿(默認緩存區大小 = 128)、被觀察者仍然繼續發送下1個事件時
- 處理方式:友好提示:緩存區滿了
// 創建被觀察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 發送 129個事件
for (int i = 0;i< 129; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.MISSING) // 設置背壓模式 = BackpressureStrategy.MISSING
.subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
模式3:BackpressureStrategy.BUFFER
- 問題:發送事件速度 > 接收事件 速度,即流速不匹配
具體表現是:出現當緩存區大小存滿(默認緩存區大小 = 128)、被觀察者仍然繼續發送下1個事件時
- 處理方式:將緩存區大小設置成無限大
- 即 被觀察者可無限發送事件 觀察者,但實際上是存放在緩存區
- 但要注意內存情況,防止出現OOM
// 創建被觀察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 發送 129個事件
for (int i = 1;i< 130; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.BUFFER) // 設置背壓模式 = BackpressureStrategy.BUFFER
.subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
可以接收超過原先緩存區大小(128)的事件數量了
模式4: BackpressureStrategy.DROP
- 問題:發送事件速度 > 接收事件 速度,即流速不匹配
具體表現是:出現當緩存區大小存滿(默認緩存區大小 = 128)、被觀察者仍然繼續發送下1個事件時
- 處理方式:超過緩存區大小(128)的事件丟棄
如發送了150個事件,僅保存第1 - 第128個事件,第129 -第150事件將被丟棄
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 發送150個事件
for (int i = 0;i< 150; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.DROP) // 設置背壓模式 = BackpressureStrategy.DROP
.subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 通過按鈕進行接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(128);
// 每次接收128個事件
}
});
被觀察者一下子發送了150個事件,點擊按鈕接收時觀察者接收了128個事件;再次點擊接收時卻無法接受事件,這說明超過緩存區大小的事件被丟棄了。
模式5:BackpressureStrategy.LATEST
- 問題:發送事件速度 > 接收事件 速度,即流速不匹配
具體表現是:出現當緩存區大小存滿(默認緩存區大小 = 128)、被觀察者仍然繼續發送下1個事件時
- 處理方式:只保存最新(最后)事件,超過緩存區大小(128)的事件丟棄
即如果發送了150個事件,緩存區里會保存129個事件(第1-第128 + 第150事件)
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i< 150; i++) {
Log.d(TAG, "發送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.LATEST) // // 設置背壓模式 = BackpressureStrategy.LATEST
.subscribeOn(Schedulers.io()) // 設置被觀察者在io線程中進行
.observeOn(AndroidSchedulers.mainThread()) // 設置觀察者在主線程中進行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 通過按鈕進行接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(128);
// 每次接收128個事件
}
});
- 被觀察者一下子發送了150個事件,點擊按鈕接收時觀察者接收了128個事件;
- 再次點擊接收時卻接收到1個事件(第150個事件),這說明超過緩存區大小的事件僅保留最后的事件(第150個事件)
5.3.3 特別注意
在使用背壓策略模式的時候,有1種情況是需要注意的:
a. 背景
FLowable
可通過自己創建(如上面例子),或通過其他方式自動創建,如interval操作符
interval操作符簡介
- 作用:每隔1段時間就產生1個數字(Long型),從0開始、1次遞增1,直至無窮大
- 默認運行在1個新線程上
- 與timer操作符區別:timer操作符可結束發送
b. 沖突
對于自身手動創建
FLowable
的情況,可通過傳入背壓模式參數選擇背壓策略
(即上面描述的)可是對于自動創建
FLowable
,卻無法手動傳入傳入背壓模式參數,那么出現流速不匹配的情況下,該如何選擇 背壓模式呢?
// 通過interval自動創建被觀察者Flowable
// 每隔1ms將當前數字(從0開始)加1,并發送出去
// interval操作符會默認新開1個新的工作線程
Flowable.interval(1, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.newThread()) // 觀察者同樣工作在一個新開線程中
.subscribe(new Subscriber<Long>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
s.request(Long.MAX_VALUE); //默認可以接收Long.MAX_VALUE個事件
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: " + aLong);
try {
Thread.sleep(1000);
// 每次延時1秒再接收事件
// 因為發送事件 = 延時1ms,接收事件 = 延時1s,出現了發送速度 & 接收速度不匹配的問題
// 緩存區很快就存滿了128個事件,從而拋出MissingBackpressureException異常,請看下圖結果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
c. 解決方案
RxJava 2.0
內部提供 封裝了背壓策略模式的方法
onBackpressureBuffer()
onBackpressureDrop()
onBackpressureLatest()
默認采用
BackpressureStrategy.ERROR
模式
具體使用如下:
Flowable.interval(1, TimeUnit.MILLISECONDS)
.onBackpressureBuffer() // 添加背壓策略封裝好的方法,此處選擇Buffer模式,即緩存區大小無限制
.observeOn(Schedulers.newThread())
.subscribe(new Subscriber<Long>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: " + aLong);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
從而很好地解決了發送事件 & 接收事件 速度不匹配的問題。
其余方法的作用類似于上面的說背壓模式參數,此處不作過多描述。
背壓策略模式小結
- 至此,對
RxJava 2.0
的背壓模式終于講解完畢 - 所有代碼Demo均存放在Carson_Ho的Github地址
6. 總結
本文主要對
Rxjava
的背壓模式知識進行講解Carson帶你學RxJava系列文章:
入門
Carson帶你學Android:這是一篇清晰易懂的Rxjava入門教程
Carson帶你學Android:面向初學者的RxJava使用指南
Carson帶你學Android:RxJava2.0到底更新了什么?
原理
Carson帶你學Android:圖文解析RxJava原理
Carson帶你學Android:手把手帶你源碼分析RxJava
使用教程:操作符
Carson帶你學Android:RxJava操作符教程
Carson帶你學Android:RxJava創建操作符
Carson帶你學Android:RxJava功能性操作符
Carson帶你學Android:RxJava過濾操作符
Carson帶你學Android:RxJava組合/合并操作符
Carson帶你學Android:RxJava變換操作符
Carson帶你學Android:RxJava條件/布爾操作符
實戰
Carson帶你學Android:什么時候應該使用Rxjava?(開發場景匯總)
Carson帶你學Android:RxJava線程控制(含實例講解)
Carson帶你學Android:圖文詳解RxJava背壓策略
Carson帶你學Android:RxJava、Retrofit聯合使用匯總(含實例教程)
Carson帶你學Android:優雅實現網絡請求嵌套回調
Carson帶你學Android:網絡請求輪詢(有條件)
Carson帶你學Android:網絡請求輪詢(無條件)
Carson帶你學Android:網絡請求出錯重連(結合Retrofit)
Carson帶你學Android:合并數據源
Carson帶你學Android:聯想搜索優化
Carson帶你學Android:功能防抖
Carson帶你學Android:從磁盤/內存緩存中獲取緩存數據
Carson帶你學Android:聯合判斷
歡迎關注Carson_Ho的簡書
不定期分享關于安卓開發的干貨,追求短、平、快,但卻不缺深度。