Rx2:小create,大文章

前言

自從去年8月底《淺談RxJava與2.0的新特性》,已經過去快一年。筆者也沒想到此文竟有讀者等筆者填坑快一年了,不禁汗顏。所以筆者打算寫關于一個 RxJava2 的系列文章,既為填坑,也為回報讀者對我的支持。本文為第一篇。

讀本系列文章,你可能有如下收獲:

  • 了解其設計原理,代碼實現
  • 掌握操作符的正確使用姿勢,避免采坑
  • 強化 Rx 編程思想,寫出更 Rx 的代碼
  • 跟讀精彩的源碼,強化編程功底

廢話不多說,進入正題。

Reactive Streams

之前在《淺談RxJava與2.0的新特性》我們提到過, RxJava2 遵循 Reactive Streams 的編程規范, 或者更精確的說,是 RxJava2 中的 Flowable 相關的類。因此我們只分析 Flowable 相關的實現與使用,剩下的 Observable、 Completable、Single、 Maybe 這些不會再提及,相信讀者朋友們可以舉一反三。

Reactive Streams 中明確規范了如下4點:

  • process a potentially unbounded number of elements
  • in sequence,
  • asynchronously passing elements between components,
  • with mandatory non-blocking backpressure.

后面筆者會用 RS 代替全稱。
請跟隨本系列文章慢慢看 Flowable 是出色的完成上述的要求。

閱讀源碼的正確姿勢

Rx2 在源碼中加入了一些注解,這些注解對運行沒有任何實際作用,僅僅是用作標識備注,有助于開發者了解某個操作符的正確使用姿勢,同時也有利于閱讀源碼時整理思路。這些注解位于io.reactivex.annotations包名下,這里著重介紹一個。

BackpressureSupport

BackpressureSupport 是用作標識這個操作符對背壓的支持類型,有以下幾種:

  • PASS_THROUGH:表示這個操作符僅僅傳遞背壓類型,不做任何改變。例如defer 操作符,這個操作符支持的背壓類型取決于Callable產生的Publisher。
  • FULL:表示這個操作符支持完全的背壓,協調上下游關系
  • SPECIAL:表示這個操作符支持的背壓類型由方法上的文檔說明
  • UNBOUNDED_IN:表示這個操作符會向上游請求 Long.MAX_VALUE,并協調下游
  • ERROR:表示如果下游沒有足夠數量的request,上游發射了超額的數據,這個操作符會拋出一個MissingBackpressureException
  • NONE:表示不處理背壓

上面這些字面解釋看起來還是很繞的,尤其是對于沒有閱讀過相關源碼的讀者。我們也不必一次性全部弄明白,后續會慢慢講清楚所有。

走進create源碼

前文中有提到過,Rx2 收回了create方法的權限,使開發者自定義的create也能夠正確的支持背壓。而實現的方式就是通過額外提供一個BackpressureStrategy參數。也因此,create的方法注解中 BackpressureSupportSPECIAL

FlowableCreate

拋開Rx2提供的 plugin 不談,本質上就是用 create 傳進來的2個參數創建了FlowableCreate這個類。
根據傳入的BackpressureStrategy生成不同的Emitter對象。并遵循一致的編程約定,先調用 onSubscribe,隨后將Emitter傳遞給FlowableOnSubscribe用來發射數據。

@Override
public void subscribeActual(Subscriber<? super T> t) {
    BaseEmitter<T> emitter;

    switch (backpressure) {
    case MISSING: {
        emitter = new MissingEmitter<T>(t);
        break;
    }
    case ERROR: {
        emitter = new ErrorAsyncEmitter<T>(t);
        break;
    }
    case DROP: {
        emitter = new DropAsyncEmitter<T>(t);
        break;
    }
    case LATEST: {
        emitter = new LatestAsyncEmitter<T>(t);
        break;
    }
    default: {
        emitter = new BufferAsyncEmitter<T>(t, bufferSize());
        break;
    }
    }

    t.onSubscribe(emitter);
    try {
        source.subscribe(emitter);
    } catch (Throwable ex) {
        Exceptions.throwIfFatal(ex);
        emitter.onError(ex);
    }
}

BackpressureStrategy

Rx2 大量的類通過繼承AtomicLong來表示計算個數與發射個數,請求一個+1,發射一個-1。并且在 Rx2 中 Long.MAX_VALUE 有特殊含義,表示無限的數據。即,如果 request(Long.MAX_VALUE),即使發射了數據也不會減少自身的數值。

這里所有的 Emitter 都繼承了基類BaseEmitter,并提供一些公共方法如setDisposable/setCancellable/requested/serialize等。然后根據各自的背壓策略,實現相應的邏輯,下面分別介紹。

MISSING

MISSING即沒有背壓,我們看 onNext 函數會發現,每調用一次就會傳遞給下游的 subscriber.onNext,空指針則onError。

@Override
public void onNext(T t) {
    if (isCancelled()) {
        return;
    }

    if (t != null) {
        actual.onNext(t);
    } else {
        onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
        return;
    }

    for (;;) {
        long r = get();
        if (r == 0L || compareAndSet(r, r - 1)) {
            return;
        }
    }
}

上面的代碼是2.1.2版本的源碼,筆者認為這里有一處 BUG 。即在自減的時候,沒有檢查 Long.MAX_VALUE 的情況,導致在request(Long.MAX_VALUE)后,發射數據時依然會不斷自減,這是與一致的設計思路相悖的。反觀下面 DROP 與 BUFFER 相關的 Emitter 處理時,則直接調用了BackpressureHelper.produced(this, 1),在里面會有 Long.MAX_VALUE 的判斷。

雖然有點小 BUG,但是實際中除了在requested()函數中會出錯外,不會影響正常的執行流。且一般開發者也不會使用Emitter.requested()函數。

雖然 MISSING 不支持背壓,但是沒關系,我們可以通過操作符來彌補。



這些操作符結合使用 MISSING 的 create 方法,使得原本不支持背壓的 Flowable 支持背壓。

當然我們也大可不必這樣麻煩,既然要使用 buffer、drop 或者 latest,使用下面的策略即可。除非我們需要那些操作符提供的額外功能。

ERROR

ERROR則和最開始的BackpressureSupport.ERROR表現一致。

下面這塊代碼是NoOverflowBaseAsyncEmitter,會被 ERROR 和 DROP 對應的Emitter繼承。邏輯也很簡單,即請求數如果還大于0,則向下發射并將請求數減1,否則走onOverflow()方法。

@Override
public final void onNext(T t) {
    if (isCancelled()) {
        return;
    }

    if (t == null) {
        onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
        return;
    }

    if (get() != 0) {
        actual.onNext(t);
        BackpressureHelper.produced(this, 1);
    } else {
        onOverflow();
    }
}

而 ERROR 對應的實現則很簡單了,不在贅述。

@Override
void onOverflow() {
    onError(new MissingBackpressureException("create: could not emit value due to lack of requests"));
}

DROP

DROP即直接丟棄超額的數據,體現在代碼中就非常簡單。

@Override
void onOverflow() {
    // nothing to do
}

BUFFER 與 LATEST

這倆之所以放到了一起,是因為 BUFFER 與 LATEST 本質上都是緩存了數據,細節上的區別就是,BUFFER 是緩存了所有數據,而 LATEST 只保留了最近的一個 onNext 數據。

體現在代碼中這兩者最主要的區別就是一個用了隊列來緩存,一個用了AtomicReference 來維持最后一個未被消費的數據。

就挑 BUFFER 來說, onNext 就是將數據扔進隊列,而后嘗試消費數據即調用drain()。onError 與 onComplete 則是將 結束標識置為 true ,并保留異常,然后依然也是在drain()中消費該消息。
在/onNext/onError/onComplete/onRequested時,都會調用drain()來消費隊列中的數據。

@Override
public void onNext(T t) {
    if (done || isCancelled()) {
        return;
    }

    if (t == null) {
        onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
        return;
    }
    queue.offer(t);
    drain();
}

drain中做的事就比較復雜了,為了保證線程安全,首先通過一個AtomicInteger來確保只有一個線程可以進入for(;;)循環。
在 for 循環中,不斷的消費隊列中的數據,如果隊列為空則檢查結束標識是否為 true,是的話則發射 onComplete 或者 onError 。

void drain() {
    if (wip.getAndIncrement() != 0) {
        return;
    }

    int missed = 1;
    final Subscriber<? super T> a = actual;
    final SpscLinkedArrayQueue<T> q = queue;

    for (;;) {
        long r = get();
        long e = 0L;

        while (e != r) {
            if (isCancelled()) {
                q.clear();
                return;
            }

            boolean d = done;

            T o = q.poll();

            boolean empty = o == null;

            if (d && empty) {
                Throwable ex = error;
                if (ex != null) {
                    error(ex);
                } else {
                    complete();
                }
                return;
            }

            if (empty) {
                break;
            }

            a.onNext(o);

            e++;
        }

        if (e == r) {
            if (isCancelled()) {
                q.clear();
                return;
            }

            boolean d = done;

            boolean empty = q.isEmpty();

            if (d && empty) {
                Throwable ex = error;
                if (ex != null) {
                    error(ex);
                } else {
                    complete();
                }
                return;
            }
        }

        if (e != 0) {
            BackpressureHelper.produced(this, e);
        }

        missed = wip.addAndGet(-missed);
        if (missed == 0) {
            break;
        }
    }
}

這里請大家留意一個編程的套路,在絕大多數隊列消費的場景里, Rx2 中都是使用了下面的方式。這也是我們可以積累使用的。通過這種方式可以保證for循環里的代碼是單線程執行的,且如果執行期間有一次或多次新的調用drain(),會導致重新走一遍包含注釋處的代碼,確保數據可以正確的消費發射。

void drain() {
    if (wip.getAndIncrement() != 0) {
        return;
    }

    int missed = 1;
    for (;;) {
        
        // 消費隊列, 發射數據

        missed = wip.addAndGet(-missed);
        if (missed == 0) {
            break;
        }
    }
}

小結

筆者在介紹過程中已經省略了很多細枝末節,不免顯得知識有些分散,結合源碼閱讀效果更佳。沒想到一個小小的 create 也包含這么多的玄機。

我相信通過閱讀這篇文章,讀者們寫 create 的時候應該可以做到結合實際場景選擇正確的BackpressureStrategy。

有了 create 便從此開啟 Rx2 萬里長征第一步。下一篇,我們將會介紹 Rx2 的線程調度相關的操作符及其實現,敬請期待。

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

推薦閱讀更多精彩內容

  • 怎么如此平靜, 感覺像是走錯了片場.為什么呢, 因為上下游工作在同一個線程呀騷年們! 這個時候上游每次調用emit...
    Young1657閱讀 1,502評論 2 1
  • 轉載自:https://xiaobailong24.me/2017/03/18/Android-RxJava2.x...
    Young1657閱讀 2,033評論 1 9
  • 背景 對于生產者和消費者模型,存在一個問題就是當生產者生產的速度大于消費者消費速度,并且生產過程不會停止,生產者和...
    風雪圍城閱讀 7,741評論 0 4
  • 作者: maplejaw本篇只解析標準包中的操作符。對于擴展包,由于使用率較低,如有需求,請讀者自行查閱文檔。 創...
    maplejaw_閱讀 45,768評論 8 93
  • 本篇文章介主要紹RxJava中操作符是以函數作為基本單位,與響應式編程作為結合使用的,對什么是操作、操作符都有哪些...
    嘎啦果安卓獸閱讀 2,889評論 0 10