Carson帶你學Android:圖文解析RxJava背壓策略


前言

Rxjava,由于其基于事件流的鏈式調用、邏輯簡潔 & 使用簡單的特點,深受各大 Android開發者的歡迎。

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

  • 本文主要講解的是RxJava中的 背壓控制策略,希望你們會喜歡。

Carson帶你學RxJava系列文章,包括 原理、操作符、應用場景、背壓等等,請關注看文章:Android:這是一份全面 & 詳細的RxJava學習指南


目錄

示意圖

1. 引言

1.1 背景

  • 觀察者 & 被觀察者 之間存在2種訂閱關系:同步 & 異步。具體如下:
示意圖
  • 對于異步訂閱關系,存在 被觀察者發送事件速度 與觀察者接收事件速度 不匹配的情況
  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
    示意圖

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.0RxJava1.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來實現背壓策略功能,即背壓策略的使用
  • FlowableObservable在功能上的區別主要是 多了背壓的功能
  • 下面,我將順著第3節中講解背壓策略實現原理 & 解決方案(如下圖),來講解Flowable在背壓策略功能上的使用
示意圖

注:

  1. 由于第2節中提到,使用背壓的場景 = 異步訂閱關系,所以下文中講解的主要是異步訂閱關系場景,即 被觀察者 & 觀察者 工作在不同線程中
  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()會實時更新觀察者能接受的事件
  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");
                    }
                });
示意圖

情況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個事件時

  • 處理方式:將緩存區大小設置成無限大
  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)的事件數量了


示意圖

模式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段時間就產生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");
                    }
                });
示意圖

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

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

封裝方法的示意圖.gif

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

背壓策略模式小結

示意圖

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的簡書

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


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容