給初學者的RxJava2.0教程(八)
前言
在上一節中, 我們學習了FLowable的一些基本知識, 同時也挖了許多坑, 這一節就讓我們來填坑吧.
正題
在上一節中最後我們有個例子, 當上遊一次性傳送128個事件的時候是沒有任何問題的, 一旦超過128就會丟擲MissingBackpressureException
異常,
提示你上游發太多事件了, 下游處理不過來, 那麼怎麼去解決呢?
我們先來思考一下, 傳送128個事件沒有問題是因為FLowable
內部有一個大小為128的水缸, 超過128就會裝滿溢位來,
那既然你水缸這麼小, 那我給你換一個大水缸
如何, 聽上去很有道理的樣子, 來試試:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; i < 1000; i++) {
Log.d(TAG, "emit " + i);
emitter.onNext(i);
}
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.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, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
這次我們直接讓上游傳送了1000個事件,下游仍然不呼叫request去請求, 與之前不同的是, 這次我們用的策略是BackpressureStrategy.BUFFER
,
這就是我們的新水缸
啦, 這個水缸就比原來的水缸牛逼多了,如果說原來的水缸是95式步槍, 那這個新的水缸就好比黃金AK
, 它沒有大小限制, 因此可以存放許許多多的事件.
所以這次的執行結果就是:
zlc.season.rxjava2demo D/TAG: onSubscribe
zlc.season.rxjava2demo D/TAG: emit 0
zlc.season.rxjava2demo D/TAG: emit 1
zlc.season.rxjava2demo D/TAG: emit 2
...
zlc.season.rxjava2demo D/TAG: emit 997
zlc.season.rxjava2demo D/TAG: emit 998
zlc.season.rxjava2demo D/TAG: emit 999
不知道大家有沒有發現, 換了水缸的FLowable和Observable好像是一樣的嘛...
不錯, 這時的FLowable表現出來的特性的確和Observable一模一樣, 因此, 如果你像這樣單純的使用FLowable, 同樣需要注意OOM的問題, 例如下面這個例子:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.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, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
按照我們以前學習Observable一樣, 讓上游無限迴圈傳送事件, 下游一個也不去處理, 來看看執行結果吧:
同樣可以看到, 記憶體迅速增長, 直到最後丟擲OOM. 所以說不要迷戀FLowable, 它只是個傳說.
可能有朋友也注意到了, 之前使用Observable測試的時候記憶體增長非常迅速, 幾秒鐘就OOM, 但這裡增長速度卻比較緩慢, 可以翻回去看之前的文章中的GIF圖進行對比, 這也看出FLowable相比Observable, 在效能方面有些不足, 畢竟FLowable內部為了實現響應式拉取做了更多的操作, 效能有所丟失也是在所難免, 因此單單只是說因為FLowable是新興產物就盲目的使用也是不對的, 也要具體分場景,
那除了給FLowable換一個大水缸還有沒有其他的辦法呢, 因為更大的水缸也只是緩兵之計啊, 動不動就OOM給你看.
想想看我們之前學習Observable的時候說到的如何解決上游傳送事件太快的, 有一招叫從數量
上取勝,
同樣的FLowable中也有這種方法, 對應的就是BackpressureStrategy.DROP
和BackpressureStrategy.LATEST
這兩種策略.
從名字上就能猜到它倆是幹啥的, Drop就是直接把存不下的事件丟棄,Latest就是隻保留最新的事件, 來看看它們的實際效果吧.
先來看看Drop:
public static void request() {
mSubscription.request(128);
}
public static void demo3() {
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
}, BackpressureStrategy.DROP).subscribeOn(Schedulers.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, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
}
我們仍然讓上游無限迴圈傳送事件, 這次的策略選擇了Drop, 同時把Subscription儲存起來, 待會我們在外部呼叫request(128)時, 便可以看到執行的結果.
我們先來猜一下執行結果, 這裡為什麼request(128)呢, 因為之前不是已經說了嗎, FLowable內部的預設的水缸大小為128, 因此, 它剛開始肯定會把0-127這128個事件儲存起來, 然後丟棄掉其餘的事件, 當我們request(128)的時候,下游便會處理掉這128個事件, 那麼上游水缸中又會重新裝進新的128個事件, 以此類推, 來看看執行結果吧:
從執行結果中我們看到的確是如此, 第一次request的時候, 下游的確收到的是0-127這128個事件, 但第二次request的時候就不確定了, 因為上游一直在傳送事件. 記憶體佔用也很正常, drop的作用相信大家也很清楚了.
再來看看Latest吧:
public static void request() {
mSubscription.request(128);
}
public static void demo4() {
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
}, BackpressureStrategy.LATEST).subscribeOn(Schedulers.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, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
}
同樣的, 上游無限迴圈傳送事件, 策略選擇Latest, 同時把Subscription儲存起來, 方便在外部呼叫request(128).來看看這次的執行結果:
誒, 看上去好像和Drop差不多啊, Latest也首先儲存了0-127這128個事件, 等下游把這128個事件處理了之後才進行之後的處理, 光從這裡沒有看出有任何區別啊...
古人云,師者,所以傳道受業解惑也。人非生而知之者,孰能無惑?惑而不從師,其為惑也,終不解矣.
作為初學者的入門導師
, 是不能給大家留下一點點疑惑的, 來讓我們繼續揭開這個疑問.
我們把上面兩段程式碼改良一下, 先來看看DROP的改良版:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; i < 10000; i++) { //只發1w個事件
emitter.onNext(i);
}
}
}, BackpressureStrategy.DROP).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
s.request(128); //一開始就處理掉128個事件
}
@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");
}
});
這段程式碼和之前有兩點不同, 一是上游只傳送了10000個事件, 二是下游在一開始就立馬處理掉了128個事件, 然後我們在外部再呼叫request(128)試試, 來看看執行結果:
這次可以看到, 一開始下游就處理掉了128個事件, 當我們再次request的時候, 只得到了第3317的事件, 後面的事件直接被拋棄了.
再來看看Latest的執行結果吧:
從執行結果中可以看到, 除去前面128個事件, 與Drop不同, Latest總是能獲取到最後最新的事件, 例如這裡我們總是能獲得最後一個事件9999.
好了, 關於FLowable的策略我們也講完了, 有些朋友要問了, 這些FLowable是我自己建立的, 所以我可以選擇策略, 那面對有些FLowable並不是我自己建立的, 該怎麼辦呢? 比如RxJava中的interval操作符, 這個操作符並不是我們自己建立的, 來看下面這個例子吧:
Flowable.interval(1, TimeUnit.MICROSECONDS)
.observeOn(AndroidSchedulers.mainThread())
.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); //延時1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
interval操作符傳送Long型的事件, 從0開始, 每隔指定的時間就把數字加1併傳送出來, 在這個例子裡, 我們讓它每隔1毫秒就傳送一次事件, 在下游延時1秒去接收處理, 不用猜也知道結果是什麼:
zlc.season.rxjava2demo D/TAG: onSubscribe
zlc.season.rxjava2demo W/TAG: onError:
io.reactivex.exceptions.MissingBackpressureException: Can't deliver value 128 due to lack of requests
at io.reactivex.internal.operators.flowable.FlowableInterval$IntervalSubscriber.run(FlowableInterval.java:87)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:278)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:273)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
一執行就丟擲了MissingBackpressureException
異常, 提醒我們發太多了, 那麼怎麼辦呢,
這個又不是我們自己建立的FLowable啊...
別慌, 雖然不是我們自己建立的, 但是RxJava給我們提供了其他的方法:
- onBackpressureBuffer()
- onBackpressureDrop()
- onBackpressureLatest()
熟悉嗎? 這跟我們上面學的策略是一樣的, 用法也簡單, 拿剛才的例子現學現用:
Flowable.interval(1, TimeUnit.MICROSECONDS)
.onBackpressureDrop() //加上背壓策略
.observeOn(AndroidSchedulers.mainThread())
.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");
}
});
其餘的我就不一一列舉了.
好了, 今天的教程就到這裡吧, 這一節我們學習瞭如何使用內建的BackpressureStrategy來解決上下游事件速率不均衡的問題. 這些策略其實之前我們將Observable的時候也提到過, 其實大差不差, 只要理解了為什麼會上游發事件太快, 下游處理太慢這一點, 你就好處理了, FLowable無非就是給你封裝好了, 確實對初學者友好一點, 但是很多初學者往往只知道How, 卻不知道Why, 最重要的其實是知道why, 而不是How.
(其餘的教程大多數到這裡就結束了, 但是, 你以為FLowable就這麼點東西嗎, 騷年, Too young too simple, sometimes naive! 這僅僅是開始, 真正牛逼的還沒來呢. 敬請關注下一節, 下節見 ! )
相關文章
- 給初學者的RxJava2.0教程(三)RxJava
- 給初學者的RxJava2.0教程(七)RxJava
- 給初學者的 RxJava2.0 教程 (四)RxJava
- 給初學者的 fc 示例教程
- 給初學者的 type 命令教程
- 給初學者看的 shuf 命令教程
- [譯] 給初學者的 Jupyter Notebook 教程
- 寫給運營同學和初學者的SQL入門教程SQL
- 給初學者的Web安全指南Web
- 寫給大資料開發初學者的話 | 附教程(轉)大資料
- 寫給初學者的Linux餐前小菜Linux
- SAP UI5 初學者教程之八 - 多語言的支援試讀版UI
- [譯]寫給初學者的Tensorflow介紹[2]
- 給初學者的以太坊路線圖指南
- 一個牛人給Java初學者的建議Java
- 一份送給Java初學者的指南Java
- 給Python初學者的最好練手專案Python
- Linux入門(2)_給初學者的建議Linux
- 零基礎學Java?給初學者的建議Java
- 給初學者一些學習Python的建議Python
- 給初學者的DLL Side Loading的UAC繞過IDE
- 寫給 Linux 初學者的一封信Linux
- 初學者的機器學習入門實戰教程!機器學習
- SAP UI5 初學者教程的學習目錄UI
- Ps 初學者教程如何建立海報?
- 給Python初學者的一些程式設計技巧Python程式設計
- 寫給初學者的Linux errno 錯誤碼機制Linux
- RCA---給初學者的根本原因分析案例
- 給Java初學者福利——Java語法基礎Java
- c++ 中vector 常見用法(給初學者)C++
- 大資料學習入門難,給初學者支招大資料
- 史上最全的Android開發學習教程集錦【初學者】Android
- 初學者學Java常遇到的問題,我都給你回答了!Java
- 給Java初學者的十條小建議,快來收藏吧!Java
- Oracle資料庫初學者入門教程Oracle資料庫
- 想給視訊新增特效初學者怎麼辦?特效
- 給Java開發初學者的10個學習建議,助你學習事半功倍!Java
- 給Java開發初學者的10個學習建議,助你學習事半功倍Java
- 初學者Mybatis的初級使用MyBatis