手把手教你使用 RxJava 2.0(三)

本節介紹的是關於Flowabale的使用,以及RxJava 2.x中的backpressure的處理策略。這部分內容應當是RxJava 2.x中改動最大的一部分。但同時也能解決一些應用場景中的問題,使得我們的RxJava更加強大。

Flowable的產生

在RxJava中會經常遇到一種情況就是被觀察者發送消息十分迅速以至於觀察者不能及時的響應這些消息。
例如下面這種情況:

Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                while (true){
                    e.onNext(1);
                }
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                Thread.sleep(2000);
                System.out.println(integer);
            }
        });

被觀察者是事件的生產者,觀察者是事件的消費者。上述例子中可以看出生產者無限生成事件,而消費者每2秒才能消費一個事件,這會造成事件無限堆積,最後造成OOM。

因此問題來了,要怎麼處理這些慢慢堆積起來的消息呢?

Flowable就是由此產生,專門用來處理這類問題。
關於上述的問題,有個專有的名詞來形容上述現象,即:Backpressure(背壓)。所謂背壓,即生產者的速度大於消費者的速度帶來的問題。
在原來的RxJava 1.x版本中並沒有Flowable的存在,Backpressure問題是由Observable來處理的。在RxJava 2.x中對於backpressure的處理進行了改動,爲此將原來的Observable拆分成了新的Observable和Flowable,同時其他相關部分也同時進行了拆分。原先的Observable已經不具備背壓處理能力。

到此,我們便知道了Flowable是爲了應對Backpressure而產生的。Flowable是一個被觀察者,與Subscriber(觀察者)配合使用,解決Backpressure問題。

下面我們就具體講解處理Backpressure的策略。
注意:處理Backpressure的策略僅僅是處理Subscriber接收事件的方式,並不影響Flowable發送事件的方法。即使採用了處理Backpressure的策略,Flowable原來以什麼樣的速度產生事件,現在還是什麼樣的速度不會變化,主要處理的是Subscriber接收事件的方式。

處理Backpressure的策略

在講具體策略之前,我們要具體分析下什麼情況下才會產生Backpressure問題?
1.如果生產者和消費者在一個線程的情況下,無論生產者的生產速度有多快,每生產一個事件都會通知消費者,等待消費者消費完畢,再生產下一個事件。所以在這種情況下,根本不存在Backpressure問題。即同步情況下,Backpressure問題不存在。
2.如果生產者和消費者不在同一線程的情況下,如果生產者的速度大於消費者的速度,就會產生Backpressure問題。即異步情況下,Backpressure問題纔會存在。

現在我們已經知道了具體產生Backpressure問題的原因及場景。那我們就可以通過學習下面處理Backpressure問題的策略來解決問題了。
以下實例都是異步操作

ERROR

這種方式會在產生Backpressure問題的時候直接拋出一個異常,這個異常就是著名的MissingBackpressureException。

我們先以代碼示例介紹一下Flowable相比與Observable新的東西。

Flowable<Integer> flowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                Log.d(TAG, "emit 1");
                emitter.onNext(1);
                Log.d(TAG, "emit 2");
                emitter.onNext(2);
                Log.d(TAG, "emit 3");
                emitter.onNext(3);
                Log.d(TAG, "emit complete");
                emitter.onComplete();
            }
        }, BackpressureStrategy.ERROR); //增加了一個參數

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            @Override
            public void onSubscribe(Subscription s) {
                Log.d(TAG, "onSubscribe");
                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");
            }
        };
        flowable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);

01-20 16:18:58.898 17829-17851/? D/MainActivity: emit 1
01-20 16:18:58.898 17829-17851/? D/MainActivity: emit 2
01-20 16:18:58.898 17829-17851/? D/MainActivity: emit 3
01-20 16:18:58.898 17829-17851/? D/MainActivity: emit complete
01-20 16:18:58.908 17829-17829/? D/MainActivity: onNext: 1
01-20 16:18:58.908 17829-17829/? D/MainActivity: onNext: 2
01-20 16:18:58.908 17829-17829/? D/MainActivity: onNext: 3
01-20 16:18:58.908 17829-17829/? D/MainActivity: onComplete

上述代碼創建了一個Flowable(被觀察者)和一個Subscriber(觀察者),可以看到程序如我們預期的一樣輸出結果了。不同的是 onSubscribe(Subscription s)中傳給我們的不再是Disposable了, 而是Subscription。然而Subscription也可以用於切斷觀察者與被觀察者之間的聯繫,調用Subscription.cancel()方法便可。 不同的地方在於Subscription增加了一個void request(long n)方法, 這個方法有什麼用呢, 在上面的代碼中也有這麼一句代碼:

  s.request(Long.MAX_VALUE);  

這個方法就是用來向生產者申請可以消費的事件數量。這樣我們便可以根據本身的消費能力進行消費事件。
當調用了request()方法後,生產者便發送對應數量的事件供消費者消費。
這是因爲Flowable在設計的時候採用了一種新的思路也就是響應式拉取的方式,你要求多少,我便傳給你多少。

注意:如果不顯示調用request就表示消費能力爲0。

雖然並不限制向request()方法中傳入任意數字,但是如果消費者並沒有這麼多的消費能力,依舊會造成資源浪費,最後產生OOM。形象點就是不能打腫臉充胖子。
而ERROR策略就避免了這種情況的出現(講了這麼多終於出現了)。

在異步調用時,RxJava中有個緩存池,用來緩存消費者處理不了暫時緩存下來的數據,緩存池的默認大小爲128,即只能緩存128個事件。無論request()中傳入的數字比128大或小,緩存池中在剛開始都會存入128個事件。當然如果本身並沒有這麼多事件需要發送,則不會存128個事件。
在ERROR策略下,如果緩存池溢出,就會立刻拋出MissingBackpressureException異常。

Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                for (int i = 0; i < 129; i++) {
                    Log.d(TAG, "emit " + i);
                    emitter.onNext(i);
                }
            }
        }, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        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() {
                    }
                });

01-20 16:58:48.993 32434-32474/? D/MainActivity: emit 125
01-20 16:58:48.993 32434-32474/? D/MainActivity: emit 126
01-20 16:58:48.993 32434-32474/? D/MainActivity: emit 127
01-20 16:58:48.993 32434-32474/? D/MainActivity: emit 128
01-20 16:58:49.003 32434-32434/? W/MainActivity: onError:
io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests

我們讓Flowable發送129個事件,而Subscriber一個也不處理,就產生了異常。
因此,ERROR即保證在異步操作中,事件累積不能超過128,超過即出現異常。消費者不能再接收事件了,但生產者並不會停止。

BUFFER

所謂BUFFER就是把RxJava中默認的只能存128個事件的緩存池換成一個大的緩存池,支持存很多很多的數據。
這樣,消費者通過request()即使傳入一個很大的數字,生產者也會生產事件,並將處理不了的事件緩存。
但是這種方式任然比較消耗內存,除非是我們比較瞭解消費者的消費能力,能夠把握具體情況,不會產生OOM。
總之BUFFER要慎用。

DROP

看名字就可以瞭解其作用:當消費者處理不了事件,就丟棄。
消費者通過request()傳入其需求n,然後生產者把n個事件傳遞給消費者供其消費。其他消費不掉的事件就丟掉。
下面例子具體介紹:

點擊“開始”按鈕,建立連接。生產者開始生產事件,剛開始消費者通過request()只要了50個事件消費。然後每次點擊“消費”按鈕,再次消費50個事件。

mFlowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                for (int i = 0; ; i++) {

                    emitter.onNext(i);
                }
            }
        }, BackpressureStrategy.DROP);
        mSubscriber = new Subscriber<Integer>() {
            @Override
            public void onSubscribe(Subscription s) {
                mSubscription = s;
                s.request(50);
            }

            @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() {

            }
        };
    }
    public void start(View view){
        mFlowable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(mSubscriber);

    }
    public void consume(View view){
        mSubscription.request(50);

    }

01-20 17:25:44.331 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 0
………………………………………………….
01-20 17:25:44.331 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 49
01-20 17:25:47.891 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 50
………………………………………………….
01-20 17:25:47.891 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 99
01-20 17:25:50.241 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 100
………………………………………………….
01-20 17:25:50.241 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 127
01-20 17:25:50.241 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 17749078
………………………………………………….
01-20 17:25:50.241 3327-3327/com.lvr.rxjavalearning D/MainActivity: onNext: 17749099

可以看出,生產者一次性傳入128個事件進入緩存池。點擊“開始”按鈕,消費了50個。然後第一次點擊“消費”按鈕,又消費了50個,第二次點擊“消費”按鈕,再次消費50個。然而此時原來的128個緩存只剩下28個了,所以先消費掉28個,然後剩下22個是後來傳入的(其實後來的是在消費了96個後傳入,並一次性在緩存池中又傳入了96個,具體可以看源碼,這裏不解釋了)。

LATEST

LATEST與DROP功能基本一致。
消費者通過request()傳入其需求n,然後生產者把n個事件傳遞給消費者供其消費。其他消費不掉的事件就丟掉。
唯一的區別就是LATEST總能使消費者能夠接收到生產者產生的最後一個事件。
還是以上述例子展示,唯一的區別就是Flowable不再無限發事件,只發送1000000個。
結果如下:

01-20 17:50:30.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 0
………………………………………………….
01-20 17:50:30.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 49
01-20 17:50:31.569 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 50
………………………………………………….
01-20 17:50:32.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 100
01-20 17:50:32.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 101
………………………………………………….
01-20 17:50:32.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 127
01-20 17:50:32.459 25334-25334/com.lvr.rxjavalearning D/MainActivity: onNext: 999999

唯一的區別就在最後一行。這就是LATEST與DROP的區別。

上述例子Flowable對象的獲取都是通過create()獲取的,自然可以通過BackpressureStrategy.LATEST之類的方式指定處理背壓的策略。如果Flowable對象不是自己創建的,可以採用onBackpressureBuffer()、onBackpressureDrop()、onBackpressureLatest()的方式指定。

 Flowable.just(1).onBackpressureBuffer()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer integer) throws Exception {

                    }
                });

以上就是關於Flowable及backpressure的內容。由於水平有限,關於RxJava 2.0 的學習內容就暫時只有這麼多了。接下來會繼續學習,如果有新的理解和認識,會再來分享。

發佈了44 篇原創文章 · 獲贊 54 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章