前言
Rxjava
,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大Android
開發者的歡迎。
如果還不瞭解RxJava,請看文章:Android:這是一篇 清晰 & 易懂的Rxjava 入門教程
- 本文主要講解的是
RxJava
中的 背壓控制策略,希望你們會喜歡。
- 本系列文章主要基於
Rxjava 2.0
- 接下來的時間,我將持續推出
Android
中Rxjava 2.0
的一系列文章,包括原理、操作符、應用場景、背壓等等 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記!!
本文所有程式碼
Demo
均存放在Carson_Ho的Github地址
目錄
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
的背壓模式知識進行講解 -
接下來的時間,我將持續推出
Android
中Rxjava 2.0
的一系列文章,包括原理、操作符、應用場景、背壓等等 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記!!
請點贊!因為你的鼓勵是我寫作的最大動力!
歡迎關注Carson_Ho的稀土掘金!
不定期分享關於安卓開發的乾貨,追求短、平、快,但卻不缺深度。