RxJava 2.x知識筆記

觀察者模式的運用

傳統的Java觀察者模式可以參考此篇博客:Java觀察者模式案例簡析

RxJava 是基于Java的觀察者模式開展的。構建被觀察者(Observable/Flowable)、觀察者(Observer/Subscriber),并通過建立兩者的訂閱關系實現觀察,在事件的傳遞過程中可以對事件進行各種處理。

在rxjava 1.x、rxjava 2.x里,Observable是被觀察者,Observer是觀察者,正常邏輯是觀察者通過subscribe訂閱Observable的事件處理,當Observable發射事件時Observer接收數據。但為了保持流式API風格,觀察者訂閱被觀察者的代碼順序設計有一些調整。
如:

Observable.subscribe(Observer);

RxJava 1.x 和RxJava 2.x的主要區別

在RxJava 2.x中的觀察者模式有兩種。而Flowable作為被觀察者是專門支持背壓的。這也是RxJava 1.x 和RxJava 2.x的主要區別。當然還有一些區別是操作符、接口的不兼容更新。

  • Observable ( 被觀察者 ) / Observer ( 觀察者 )
  • Flowable (被觀察者)/ Subscriber (觀察者)


    image.png

RxJava1.x 平滑升級到RxJava2.x

由于RxJava2.0變化較大無法直接升級,幸運的是,官方提供了RxJava2Interop這個庫,可以方便地將RxJava1.x升級到RxJava2.x,或者將RxJava2.x轉回RxJava1.x。
RxJava2Interop

經典流式API調用風格

  Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                e.onNext(1);
                e.onNext(2);
                e.onNext(3);
            }
        })
//                ...省略很多在發射過程中的流式處理代碼
                .subscribe(new Observer<Integer>() {
                    private Disposable mDisposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        mDisposable = d;
                    }

                    @Override
                    public void onNext(Integer integer) {
                        Log.d("onNext", "" + integer);
                        //新增的Disposable可以做到切斷的操作,讓Observer觀察者不再接收上游事件。
                        if (integer == 3) {
                            mDisposable.dispose();
                            Log.d("onNext", "已停止接收事件");
                        }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });

打印結果:

11-28 13:00:42.195 29930-29930/? D/onNext: 1
11-28 13:00:42.195 29930-29930/? D/onNext: 2
11-28 13:00:42.195 29930-29930/? D/onNext: 3
11-28 13:00:42.195 29930-29930/? D/onNext: 已停止接收事件

Rxjava 線程調度

subscribeOn() 指定的就是發射事件的線程,observerOn 指定的就是訂閱者接收事件的線程。

多次指定發射事件的線程只有第一次指定的有效,也就是說多次調用 subscribeOn() 只有第一次的有效,其余的會被忽略。
但多次指定訂閱者接收線程是可以的,也就是說每調用一次 observerOn(),下游的線程就會切換一次。

Rxjava 2.x常用操作符

1,Function<T, R> ——將輸入的value類型T轉換成輸出的value類型R。通常結合Map操作符。

/**
 * A functional interface that takes a value and returns another value, possibly with a
 * different type and allows throwing a checked exception.
 *
 * @param <T> the input value type
 * @param <R> the output value type
 */
public interface Function<T, R> {
    /**
     * Apply some calculation to the input value and return some other value.
     * @param t the input value
     * @return the output value
     * @throws Exception on error
     */
    @NonNull
    R apply(@NonNull T t) throws Exception;
}

2,Map——將一個Observable被觀察者通過特定函數的執行,轉換成另一種Observable被觀察者。
在 2.x 中和 1.x 中作用幾乎一致,不同點在于:2.x 將 1.x 中的 Func1 和 Func2 改為了 Function 和 BiFunction。

  /**
     * Returns an Observable that applies a specified function to each item emitted by the source ObservableSource and emits the results of these function applications.
     * 
    @SchedulerSupport(SchedulerSupport.NONE)
    public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) {
        ObjectHelper.requireNonNull(mapper, "mapper is null");
        return RxJavaPlugins.onAssembly(new ObservableMap<T, R>(this, mapper));
    }

3,Consumer——接收一個單獨的數據,類似于一個簡化版的觀察者observer。

/**
 * A functional interface (callback) that accepts a single value.
 * @param <T> the value type
 */
public interface Consumer<T> {
    /**
     * Consume the given value.
     * @param t the value
     * @throws Exception on error
     */
    void accept(@NonNull T t) throws Exception;
}

4,distinct——去重操作符。即先有的數字保留,重復的數字去除并保留原先順序的方式輸出。

  Observable.just(2, 1, 2, 3, 4, 2, 3)
                .distinct()
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Log.d("accept", "" + integer);
                    }
                });

輸出

11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 2
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 1
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 3
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 4

5,concat—— 可以做到不交錯的發射兩個甚至多個 Observable 的發射事件,并且只有前一個 Observable 終止(onComplete) 后才會訂閱下一個 Observable。比如可以采用 concat 操作符先讀取緩存再通過網絡請求獲取數據。

案例說明:

  Observable observable = Observable.just(1, 2, 3, 4, 5, 6)
                .map(new Function<Integer, Integer>() {
                    @Override
                    public Integer apply(@NonNull Integer integer) throws Exception {
                        return integer + 1;
                    }
                });
        Observable.concat(Observable.just(-1, -2, -3, -4, -5, -6), observable)
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Log.d("accept", "" + integer);
                    }
                });

打印輸出:看吧,兩個Observable是按照順序依次無交錯執行的。

11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -1
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -2
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -3
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -4
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -5
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -6
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 2
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 3
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 4
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 5
11-29 01:48:50.431 31564-31564/com.zjrb.sjzsw D/accept: 6
11-29 01:48:50.431 31564-31564/com.zjrb.sjzsw D/accept: 7

注:熟悉操作符的目的在于,不同場景中都能隨時想到有對應的工具可用。

背壓

背壓:指在異步場景中,被觀察者發送事件速度遠快于觀察者的處理速度的情況下,一種告訴上游的被觀察者降低發送速度的策略。
因為事件產生的速度遠遠快于事件消費的速度,最終導致數據積累越來越多,從而導致OOM等異常。這就是背壓產生的必要性。

RxJava2.0中,Flowable是能夠支持Backpressure的Observable,是對Observable的補充(而不是替代)。所以Observable被觀察者支持的API,Flowable也都支持,并且Flowable的API里也都強制支持背壓。

背壓經典代碼

Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                if (e.requested() != 0) {
                    for (int i = 0; i < 10; i++) {
                        e.onNext(i + 1);
                        Log.d(TAG, "已發送" + (i + 1) + "個——剩下" + e.requested());
                    }
                    e.onComplete();
                }
            }
        }, BackpressureStrategy.LATEST)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        subscription = s;
                    }

                    @Override
                    public void onNext(Integer s) {
                        Log.d(TAG, "接收 ——" + s);
                        if (s == 9){
                            subscription.cancel();
                        }
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.d(TAG, "接收錯誤——" + t);
                    }

                    @Override
                    public void onComplete() {
                    }
                });

外部調用subscription請求配合配額11個。

  if (subscription != null) {
      subscription.request(11);
  }

在BackpressureStrategy.LATEST背壓策略下,上游發射10個事件,下游由外部調用請求發布配額指令,當下游接收到第9個事件時暫停上游發布(此操作會清空上游事件源)。

背壓策略

  • BackpressureStrategy.MISSING
    此策略下,上游發射的數據不做緩存也不丟棄,下游處理溢出的問題。簡單說就是沒有背壓。
  • BackpressureStrategy.ERROR
    此策略下,在上游發射速度過快并超出下游接收速度時,拋出MissingBackpressureException異常。
  • BackpressureStrategy.BUFFER
    此策略下,把上游發射過來的所有數據全部緩存在緩存區,不做丟棄,待下游接收。
  • BackpressureStrategy.DROP
    此策略下,相當于一種令牌機制(或者配額機制),下游通過request請求產生令牌(配額)給上游,上游接到多少令牌,就給下游發送多少數據。當令牌數消耗到0的時候,上游開始丟棄數據。BackpressureStrategy.LATEST的策略和此類似。
  • BackpressureStrategy.LATEST
    此策略和BackpressureStrategy.DROP的策略類似,但在令牌數為0的時候有一點微妙的區別:onBackpressureDrop直接丟棄數據,不緩存任何數據;而onBackpressureLatest則緩存最新的一條數據,這樣當上游接到新令牌的時候,它就先把緩存的上一條“最新”數據發送給下游。
/**
 * Represents the options for applying backpressure to a source sequence.
 */
public enum BackpressureStrategy {
    /**
     * OnNext events are written without any buffering or dropping.
     * Downstream has to deal with any overflow.
     * <p>Useful when one applies one of the custom-parameter onBackpressureXXX operators.
     */
    MISSING,
    /**
     * Signals a MissingBackpressureException in case the downstream can't keep up.
     */
    ERROR,
    /**
     * Buffers <em>all</em> onNext values until the downstream consumes it.
     */
    BUFFER,
    /**
     * Drops the most recent onNext value if the downstream can't keep up.
     */
    DROP,
    /**
     * Keeps only the latest onNext value, overwriting any previous value if the
     * downstream can't keep up.
     */
    LATEST
}

Flowable案例代碼

Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                e.onNext(1);
                e.onNext(2);
                e.onNext(3);
                e.onComplete();
                e.onNext(4);
            }
        }, BackpressureStrategy.ERROR)
                //下面兩行代碼執行線程切換,達到異步效果
//                .subscribeOn(Schedulers.io())
//                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        subscription = s;
//                        subscription.request(1);
                    }

                    @Override
                    public void onNext(Integer s) {
//                        subscription.request(1);
                        Log.d(TAG, "接收——" + s);
//                        subscription.cancel();
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.d(TAG, "接收錯誤——" + t);
                    }

                    @Override
                    public void onComplete() {
                    }
                });

這里指定背壓策略是BackpressureStrategy.ERROR,這種策略下執行此段代碼會報如下錯誤。

D/rxjava2: 接收錯誤——io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests

因為,上下游是同步的。上游發射了事件但是下游沒有接收,就會造成阻塞(即便上游的事件隊列長度只有3個 < 128)。為了避免ANR,就要提示MissingBackpressureException異常。

如果恢復第12、13行處理線程切換的代碼,表示上下游位于不同線程,是異步狀態。此種情形下,上游發射數據后就不會報MissingBackpressureException異常,但雖然上游能正常發射數據,下游同樣接收不到數據。

這里涉及到一個知識點:

Flowable默認事件隊列大小為128。BackpressureStrategy.BUFFER策略下事件隊列無限大,和沒有采取背壓的Observable ( 被觀察者 ) / Observer ( 觀察者 )類似了。

注:在處理同一組數據時,Observable ( 被觀察者 ) / Observer ( 觀察者 )比BackpressureStrategy.BUFFER策略下的Flowable (被觀察者)/ Subscriber (觀察者)性能更優,內存消耗更少。

在上下游異步的情況下,上游會先把事件發送到長度為128的事件隊列中,待下游發送請求數據指令后從事件隊列中拉取數據。這種“響應式拉取”的思想用于解決上下游流速不均衡的情況。

上述代碼中,第19、24行代碼是表示下游接收前、接收后發送請求配額指令給上游。也可以通過subscription.request(n);在外圍調用發送n個請求配額給上游以獲取數據。

API自帶的被觀察者的背壓策略

API內的其他被觀察者,API也為我們提供了背壓策略方法:

  • onBackpressureBuffer()
  • onBackpressureDrop()
  • onBackpressureLatest()
    示例代碼如下:
   Flowable.interval(1, TimeUnit.MICROSECONDS)
                .onBackpressureDrop()  //加上背壓策略
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Long>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        Log.d(TAG, "onSubscribe");
                        subscription = 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");
                    }
                });

如果不加背壓策略,則會報錯:

D/rxjava2: onSubscribe
W/rxjava2: onError: 
           io.reactivex.exceptions.MissingBackpressureException: Can't deliver value 128 due to lack of requests
               at io.reactivex.internal.operators.flowable.FlowableInterval$IntervalSubscriber.run(FlowableInterval.java:87)
               at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
               at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:278)
               at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:273)
               at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
               at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
               at java.lang.Thread.run(Thread.java:761)

響應式編程

當上下游在同一個線程中的時候,在下游調用request(n)就會直接改變上游中的requested的值,多次調用便會疊加這個值,而上游每發送一個事件之后便會去減少這個值,當這個值減少至0的時候,上游若繼續發送事件便會拋異常了。

案例情景一:同步環境下,在下游發出10個請求配額情況下,上游發射130個事件。

      Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                    for (int i = 0; i < 130; i++) {
                        e.onNext(i + 1);
                        Log.d(TAG, "requested—left—" + e.requested());
                    }
//                    e.onComplete();
            }
        }, BackpressureStrategy.ERROR)
                //下面兩行代碼執行線程切換,達到異步效果
//                .subscribeOn(Schedulers.io())
//                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        subscription = s;
                        subscription.request(10);
                    }

                    @Override
                    public void onNext(Integer s) {
                        Log.d(TAG, "接收 ——" + s);
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.d(TAG, "接收錯誤——" + t);
                    }

                    @Override
                    public void onComplete() {
                    }
                });

打印LOG日志:

D/rxjava2: 接收 ——1
D/rxjava2: requested—left—9
D/rxjava2: 接收 ——2
D/rxjava2: requested—left—8
D/rxjava2: 接收 ——3
D/rxjava2: requested—left—7
D/rxjava2: 接收 ——4
D/rxjava2: requested—left—6
D/rxjava2: 接收 ——5
D/rxjava2: requested—left—5
D/rxjava2: 接收 ——6
D/rxjava2: requested—left—4
D/rxjava2: 接收 ——7
D/rxjava2: requested—left—3
D/rxjava2: 接收 ——8
D/rxjava2: requested—left—2
D/rxjava2: 接收 ——9
D/rxjava2: requested—left—1
D/rxjava2: 接收 ——10
D/rxjava2: requested—left—0
D/rxjava2: 接收錯誤——io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
D/rxjava2: requested—left—0

下游不再發送請求配額時,上游的配額令牌就為0。此時上游還有事件強行發個的話,就會出現異常。這里可以驗證上面說的結論。

案例情景二:異步環境,在下游發出10個請求配額情況下,上游發射128個事件。

只是多了切換線程的代碼:

.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

打印LOG日志:

D/rxjava2: requested—left—127
D/rxjava2: requested—left—126
D/rxjava2: requested—left—125
...
D/rxjava2: requested—left—2
D/rxjava2: requested—left—1
D/rxjava2: requested—left—0
D/rxjava2: 接收 ——1
D/rxjava2: 接收 ——2
D/rxjava2: 接收 ——3
D/rxjava2: 接收 ——4
D/rxjava2: 接收 ——5
D/rxjava2: 接收 ——6
D/rxjava2: 接收 ——7
D/rxjava2: 接收 ——8
D/rxjava2: 接收 ——9
D/rxjava2: 接收 ——10

異步的情況下,上游先把事件序列內的事件發射完畢,下游才開始接收。如果上游的時間序列超過默認的128個,則上游事件發射到第129個就會報MissingBackpressureException異常,下游就接收不到事件了。這就涉及到下一個知識點了。

當上下游工作在不同的線程里時,每一個線程里都有一個requested,而我們下游調用request(1000)時,實際上改變的是下游線程中的requested,而上游中的requested的值是由RxJava內部調用request(n)去設置的,這個調用會在合適的時候自動觸發。

何時自動觸發呢?我們一起看下:

 Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                Log.d(TAG, "e.requested() == "+e.requested());
                    for (int i = 0; ; i++) {
                        boolean flag = false;
                        //這里做了一個循環,使發射循環處于保活狀態,并在適當時機繼續發射事件
                        while (e.requested() == 0){
                            if (!flag){
                                Log.d(TAG, "e.requested() == "+e.requested());
                                flag = true;
                            }
                        }
                        e.onNext(i + 1);
                        Log.d(TAG, "已發送" + (i + 1) + "個——剩下" + e.requested());
                    }
            }
        }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        subscription = s;
                    }

                    @Override
                    public void onNext(Integer s) {
                        Log.d(TAG, "接收 ——" + s);
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.d(TAG, "接收錯誤——" + t);
                    }

                    @Override
                    public void onComplete() {
                    }
                });

外部手動調用request向上游請求發射配額

 if (subscription != null) {
     subscription.request(96);
     Log.d(TAG,"下游請求了96個");
  }

打印Log日志:

D/rxjava2: e.requested() == 128
D/rxjava2: 已發送1個——剩下127
D/rxjava2: 已發送2個——剩下126
...
D/rxjava2: 已發送127個——剩下1
D/rxjava2: 已發送128個——剩下0
D/rxjava2: e.requested() == 0
D/rxjava2: 下游請求了96個
D/rxjava2: 接收 ——1
D/rxjava2: 接收 ——2
...
D/rxjava2: 接收 ——95
D/rxjava2: 接收 ——96
D/rxjava2: 已發送129個——剩下95
...
D/rxjava2: 已發送223個——剩下1
D/rxjava2: 已發送224個——剩下0
D/rxjava2: e.requested() == 0

1,2,3,4行表示啟動之后,上游自動想上游的事件序列緩存區發射128個事件。
8,9...13行表示下游手動請求96個發射配額時接收的事件。此時會自動觸發上游繼續發送事件,如14,15...17,18行,上游會自動再次發射96個事件(95-0+1=96)。

如果你下游請求95個發射配額的話,上游不會自動觸發事件發射的(這個應該是底層設置的觸發閥吧)。

因此得出結論:當下游每消費96個事件便會自動觸發內部的request()去設置上游的requested的值。

在某一些場景下,可以在發送事件前先判斷當前的requested的值是否大于0,若等于0則說明下游處理不過來了,則需要等待,

注意:是在onNext事件里,onComplete和onError事件不會消耗requested。

本章完~

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

推薦閱讀更多精彩內容

  • 轉載自:https://xiaobailong24.me/2017/03/18/Android-RxJava2.x...
    Young1657閱讀 2,036評論 1 9
  • RxJava RxJava是響應式程序設計的一種實現。在響應式程序設計中,當數據到達的時候,消費者做出響應。響應式...
    Mr槑閱讀 966評論 0 5
  • RXjava 實質是一個異步操作庫 1. 導入 2. 概念 Observable(被觀察者) : Observer...
    Lhuo閱讀 2,333評論 0 0
  • 這可能是最好的RxJava 2.x 教程(完結版) 文章鏈接:這可能是最好的RxJava 2.x 入門教程(一)這...
    Thor_果凍閱讀 566評論 0 1
  • 再怎么憧憬,我的憧憬,我一定能要他不會落空,做不到也行
    海獅會帶給你傷害閱讀 236評論 1 0