Android RxJava :圖文詳解 背壓策略

weixin_34054866發表於2018-01-02
944365-207a738cb165a2da.png

前言

  • Rxjava,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大 Android開發者的歡迎。
944365-d0d78b3b826170ed.png
Github截圖

如果還不瞭解RxJava,請看文章:Android:這是一篇 清晰 & 易懂的Rxjava 入門教程

  • 本文主要講解的是RxJava中的 背壓控制策略,希望你們會喜歡。
  1. 本系列文章主要基於 Rxjava 2.0
  2. 接下來的時間,我將持續推出 AndroidRxjava 2.0 的一系列文章,包括原理、操作符、應用場景、背壓等等 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記!!
944365-4c1c1eb44ffe01e5.png
示意圖

本文所有程式碼 Demo均存放在Carson_Ho的Github地址


目錄

944365-0ed59b40d7b73f7c.png
示意圖

1. 引言

1.1 背景

  • 觀察者 & 被觀察者 之間存在2種訂閱關係:同步 & 非同步。具體如下:
944365-a8ca5dd7f71bd781.png
示意圖
  • 對於非同步訂閱關係,存在 被觀察者傳送事件速度 與觀察者接收事件速度 不匹配的情況
  1. 傳送 & 接收事件速度 = 單位時間內 傳送&接收事件的數量
  2. 大多數情況,主要是 被觀察者傳送事件速度 > 觀察者接收事件速度

1.2 問題

  • 被觀察者 傳送事件速度太快,而觀察者 來不及接收所有事件,從而導致觀察者無法及時響應 / 處理所有傳送過來事件的問題,最終導致快取區溢位、事件丟失 & OOM
  1. 如,點選按鈕事件:連續過快的點選按鈕10次,則只會造成點選2次的效果;
  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
    944365-c85cb4bdb95c17b5.gif
    示意圖

1.3 解決方案

採用 背壓策略。

下面,我將開始介紹背壓策略。


2. 背壓策略簡介

2.1 定義

一種 控制事件流速 的策略

2.2 作用

非同步訂閱關係 中,控制事件傳送 & 接收的速度

注:背壓的作用域 = 非同步訂閱關係,即 被觀察者 & 觀察者處在不同執行緒中

2.3 解決的問題

解決了 因被觀察者傳送事件速度 與 觀察者接收事件速度 不匹配(一般是前者 快於 後者),從而導致觀察者無法及時響應 / 處理所有 被觀察者傳送事件 的問題

2.4 應用場景

  • 被觀察者傳送事件速度 與 觀察者接收事件速度 不匹配的場景
  • 具體場景就取決於 該事件的型別,如:網路請求,那麼具體場景:有很多網路請求需要執行,但執行者的執行速度沒那麼快,此時就需要使用背壓策略來進行控制。

3. 背壓策略的原理

  • 那麼,RxJava實現背壓策略(Backpressure)的原理是什麼呢?
  • 解決方案 & 思想主要如下:
944365-37ae2f5f93d9326c.png
示意圖
  • 示意圖如下
944365-d37b89b86aea104d.png
示意圖
  • RxJava1.0 中被觀察者的舊實現 Observable 對比
944365-c01363ed15386193.png
示意圖
  • 好了,那麼上圖中在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的特點 具體如下
944365-ceca5a724ce25985.png
示意圖
  • 下面再貼出一張RxJava2.0RxJava1.0的觀察者模型的對比圖

實際上,RxJava2.0 也有保留(被觀察者)Observerble - Observer(觀察者)的觀察者模型,此處只是為了做出對比讓讀者瞭解

944365-9c67239dfbc77eed.png
示意圖

4.3 與 RxJava1.0 中被觀察者的舊實現 Observable 的關係

  • 具體如下圖
944365-025e8828a7dd1fd9.png
示意圖
  • 那麼,為什麼要採用新實現Flowable實現背壓,而不採用舊的Observable呢?
  • 主要原因:舊實現Observable無法很好解決背壓問題。
944365-2a2dac291a1ec818.png
示意圖

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);

944365-dfd85d96ec5dc33c.png
示意圖
  • 更加優雅的鏈式呼叫
        // 步驟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來實現背壓策略功能,即背壓策略的使用
  • FlowableObservable在功能上的區別主要是 多了背壓的功能
  • 下面,我將順著第3節中講解背壓策略實現原理 & 解決方案(如下圖),來講解Flowable在背壓策略功能上的使用
944365-e0ec10d25ccf64dd.png
示意圖

注:

  1. 由於第2節中提到,使用背壓的場景 = 非同步訂閱關係,所以下文中講解的主要是非同步訂閱關係場景,即 被觀察者 & 觀察者 工作在不同執行緒中
  2. 但由於在同步訂閱關係的場景也可能出現流速不匹配的問題,所以在講解非同步情況後,會稍微講解一下同步情況,以方便對比

5.1 控制 觀察者接收事件 的速度

5.1.1 非同步訂閱情況
  • 簡介
944365-c016b71a080265b0.png
示意圖
  • 具體原理圖
944365-3eebc18cf5bfe45b.png
示意圖
  • 具體使用
// 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");
                    }
                });

  • 效果圖
944365-b4ac77634720e781.png
示意圖
  • 有2個結論是需要大家注意的
944365-bd5cdcab76667918.png
示意圖

下圖 = 當快取區存滿時(128個事件)溢位報錯的原理圖

944365-30fdc84d48e39456.png
示意圖
  • 程式碼演示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");
                    }
                });

944365-863d659e1f6f019c.gif
示意圖
  • 程式碼演示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");
                    }
                });
944365-f8f1703de76cb265.png
示意圖

5.1.2 同步訂閱情況

同步訂閱 & 非同步訂閱 的區別在於:

  • 同步訂閱中,被觀察者 & 觀察者工作於同1執行緒
  • 同步訂閱關係中沒有快取區
944365-1fe35f31544966ed.png
示意圖
  • 被觀察者在傳送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);
944365-f62210c0e87994b3.png
示意圖

所以,實際上並不會出現被觀察者傳送事件速度 > 觀察者接收事件速度的情況。可是,卻會出現被觀察者傳送事件數量 > 觀察者接收事件數量的問題。

  • 如:觀察者只能接受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);
944365-b8f4f40ef2caaec3.png
示意圖

所以,對於沒有快取區概念的同步訂閱關係來說,單純採用控制觀察者的接收事件數量(響應式拉取)實際上就等於 “單相思”,雖然觀察者控制了要接收3個事件,但假設被觀察者需要傳送4個事件,還是會出現問題。

在下面講解 5.2 控制被觀察者傳送事件速度 時會解決這個問題。

  • 有1個特殊情況需要注意
944365-deee1639cd045920.png
示意圖
  • 程式碼演示
/**
  * 同步情況
  */

        /**
         * 步驟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異常 & 觀察者沒有收到任何事件

944365-29cebc2be381fbea.png
示意圖

5.2 控制 被觀察者傳送事件 的速度

  • 簡介
944365-f18a40b597c68c8f.png
示意圖
  • 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值

  • 對應於同步 & 非同步訂閱情況 的原理圖

944365-88e1f3c641eb54e3.png
示意圖

為了方便大家理解該策略中的requested()使用,該節會先講解同步訂閱情況,再講解非同步訂閱情況


5.2.1 同步訂閱情況

  • 原理說明
944365-233a841e920c83e0.png
示意圖

即在同步訂閱情況中,被觀察者 通過 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");
                    }
                });
944365-9c66a73f6bc4d108.png
示意圖
  • 特別注意
    在同步訂閱情況中使用FlowableEmitter.requested()時,有以下幾種使用特性需要注意的:
944365-bf897a2c6d664bbb.png
示意圖

情況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");
                    }
                });
944365-d9fa2818e086fc9f.png
示意圖

情況2:實時更新性

  • 即,每次傳送事件後,emitter.requested()會實時更新觀察者能接受的事件
  1. 即一開始觀察者要接收10個事件,傳送了1個後,會實時更新為9個
  2. 僅計算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");
                    }
                });
944365-fd91525258e3feeb.png
示意圖

情況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");
                    }
                });
944365-acbf17d1cf200c0c.png
示意圖

額外

  • 若觀察者沒有設定可接收事件數量,即無呼叫Subscription.request()
  • 那麼被觀察者預設觀察者可接收事件數量 = 0,即FlowableEmitter.requested()的返回值 = 0

5.2.2 非同步訂閱情況

  • 原理說明
944365-45e520e7016cf9fd.png
示意圖

從上面可以看出,由於二者處於不同執行緒,所以被觀察者 無法通過 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");
                    }
                });
944365-1d94a2a6a7c510f2.png
示意圖

而在非同步訂閱關係中,反向控制的原理是:通過RxJava內部固定呼叫被觀察者執行緒中的request(n) 從而 反向控制被觀察者的傳送事件速度

那麼該什麼時候呼叫被觀察者執行緒中的request(n) & n 的值該是多少呢?請繼續往下看。

  • 具體使用

關於RxJava內部呼叫request(n)(n = 128、96、0)的邏輯如下:

944365-f6314aba60c08455.png
示意圖

至於為什麼是呼叫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個事件
            }

        });

整個流程 & 測試結果 請看下圖

944365-c3c362cd8e101867.png
示意圖

5.3 採用背壓策略模式:BackpressureStrategy

5.3.1 背壓模式介紹

在Flowable的使用中,會被要求傳入背壓模式引數

944365-ccd84761c65ddaa6.png
示意圖
  • 物件導向:針對快取區
  • 作用:當快取區大小存滿、被觀察者仍然繼續傳送下1個事件時,該如何處理的策略方式

快取區大小存滿、溢位 = 傳送事件速度 > 接收事件速度 的結果 = 傳送 & 接收事件不匹配的結果

5.3.2 背壓模式型別

944365-47b55edec299faea.png
示意圖

下面我將對每種模式逐一說明。

模式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");
                    }
                });
944365-0c56eb0868106c41.png
示意圖

模式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");
                    }
                });
944365-8f27f7fe6258bea6.png
示意圖

模式3:BackpressureStrategy.BUFFER

  • 問題:傳送事件速度 > 接收事件 速度,即流速不匹配

具體表現是:出現當快取區大小存滿(預設快取區大小 = 128)、被觀察者仍然繼續傳送下1個事件時

  • 處理方式:將快取區大小設定成無限大
  1. 即 被觀察者可無限傳送事件 觀察者,但實際上是存放在快取區
  2. 但要注意記憶體情況,防止出現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)的事件數量了


944365-f1fffce5c7925567.png
示意圖

模式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個事件;再次點選接收時卻無法接受事件,這說明超過快取區大小的事件被丟棄了。


944365-6b601cfdcaa0eb29.gif
示意圖

模式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個事件)
944365-006167898961a15b.gif
示意圖

5.3.3 特別注意

在使用背壓策略模式的時候,有1種情況是需要注意的:

a. 背景
FLowable 可通過自己建立(如上面例子),或通過其他方式自動建立,如interval操作符

interval操作符簡介

  1. 作用:每隔1段時間就產生1個數字(Long型),從0開始、1次遞增1,直至無窮大
  2. 預設執行在1個新執行緒上
  3. 與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");
                    }
                });
944365-ff404e17814378eb.png
示意圖

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");
                    }
                });

從而很好地解決了傳送事件 & 接收事件 速度不匹配的問題。

944365-814e7dcd89ae95b7.gif
封裝方法的示意圖.gif

其餘方法的作用類似於上面的說背壓模式引數,此處不作過多描述。

背壓策略模式小結

944365-1be234a3c566c7bc.png
示意圖

6. 總結

  • 本文主要對 Rxjava 的背壓模式知識進行講解

  • 接下來的時間,我將持續推出 AndroidRxjava 2.0 的一系列文章,包括原理、操作符、應用場景、背壓等等 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記!!

944365-4c1c1eb44ffe01e5.png
示意圖

請點贊!因為你的鼓勵是我寫作的最大動力!

相關文章閱讀


歡迎關注Carson_Ho的簡書!

不定期分享關於安卓開發的乾貨,追求短、平、快,但卻不缺深度

944365-9b76fa3c52d478a7.png

相關文章