深入理解 RxJava2:前世今生(1)

前言

本系列文章適用于已經了解 RxJava 的讀者,深入貫徹其原理,加深對其的認識。如果從未了解過 RxJava 的讀者們,建議先熟悉

RxJava 0.x

RxJava 最早是 Netflix 參照微軟的 Rx.Net,在 Java 上實現一套類似的庫,0.x 其實就是社區內部迭代開發的時代。

在 0.x 的迭代過程中,API 還不穩定,在長期的變更中,逐步完善了 Observable,Publisher,Subscriber,Scheduler 等接口以及大量的操作符。

Reactive Streams

在開發 RxJava 早期版本的過程中,開發組也參與了制定 Reactive Streams 規范。

但是 RxJava1 并沒有遵循這個規范,因為考慮到下面幾個原因:

  1. Subscriber.onComplete 和 當時的 Observer.onCompleted 相差一個字母,導致 API 不能兼容。
  2. RxJava 中的 Subscription 和 RS 中的有些細微的區別,它不能在沒有較大變動的情況下實現 request(n) 方法,因此 RxJava 引入了 Producer 這個接口,并混在了 Subscriber 中。
  3. RS 彼時不是穩定版本,也在持續修訂中。

因此 RxJava 開發組決定在 2.0 版本中正式支持 RS 規范,在 1.x 版本中實現類似的機制,而不像在 2.0 中直接使用 RS 的接口。

Backpressure

由于 RS 的影響,在 0.20.0-RC1 中 RxJava 第一次引入 Backpressure 的概念,從此 RxJava 變成了一個讓人愛恨交織的庫。事實上 RxJava 開發組也曾表示,在 RxJava 早期版本中,在 Observable 混入 Backpressure 是一個重大的失誤。

  • 由于 Backpressure 是在中間版本引入的,因此部分操作符支持,部分操作符不支持,導致對使用者有些混亂和不友好。
  • 在熱數據源中(如點擊事件),是無法被正確地 backpressured ,從而導致經常出現意外的 MissingBackpressureException

事實上正如現在 2.x 中做的那樣,正確的做法是應該把模塊分為 支持 Backpressure 和不支持的兩類。在 io.reactivex.Observable 中徹底移除了 Backpressure,而 io.reactivex.Flowable 則遵循 RS 規范支持 Backpressure。

RxJava 1.x

經過兩年多的迭代,RxJava 在 14 年 9 月發布了 1.0.0 正式版。
上面有提到,其實 1.x 是一個類似 RS 的版本,但是不依賴 RS 的接口。同時對比 0.x,做了如下的更改。

依賴方式
groupId artifactId
0.x com.netflix.rxjava rxjava-core
1.x io.reactivex rxjava
拆分項目

在 RxJava 1.0.0 發布之際,把 JVM 上其他語言的實現和子工程都獨立出去了,而在 RxJava 庫中只保留了 Java 版的實現。
如:

其他

新增和廢棄了部分操作符,修復了大量的 BUG。

RxJava 2.x

RxJava 2.0.0 正式版發布于 2016 年 9 月底。
筆者也曾寫過一篇 《淺談RxJava與2.0的新特性》,不過寫那篇文章時還在版本還在 2.0.0-RC1,以現在的角度看起來不免顯得不夠全面,因此最好的理解方式還是看官方的 Wiki

類型

1.x 2.x
Single/Completable Maybe 0 或 1 數據源
Observable Flowable 多數據源
Subject Processor 熱數據源

正如上面所說的,RxJava2 遵循了 RS 規范,其冷數據源真正實現的類型就是 Flowable,熱數據源的實現則在io.reactivex.processors包中。
同時也把 Observable 中舊的 Backpressure 徹底移除,因此在 RxJava2 中使 用 Observable 再也不會拋出MissingBackpressureException

Observable 與 Flowable

在 RxJava2 中, Flowable 和 Observable 雖然實現的代碼復用了一部分,但是機制卻大相徑庭。這里要涉及到數據源的三種模型:

數據源
  • 響應式推:如鼠標點擊事件。此時生產者無條件產生數據,消費者負責配合生產者。
  • 同步拉:如Iterable的迭代。此時消費者提出要求,生產者配合消費者下發,數據源是確定的。
  • 異步拉:如FutureProcessor。消費者提出要求,生產者據此下發數據,但是數據到來不確定。
Observable

在 Observable 中,消費者是無權提出要求的,即數據都是生產者提供的,消費者只能被動接受。雖然數據源不確定,但是對消費者是透明的,只能被動等待數據。由于 Observable 已經徹底移除了 Backpressure,因此對于消費速度和生產速度不協調時,中間操作符可能會創建 Buffer(如 observeOn)來緩存數據。因此數據在無限的積累中可能會導致 OOM, 但不再會拋出MissingBackpressureException

Flowable

在 Flowable 中,消費者通過Subscription主動的向生產者提出自己需求的數量,上游據此發射數據。從而就有生產和消費的矛盾。如果是數據源是響應式推或者異步拉時,可能會導致MissingBackpressureException

舉例

這么說有點抽象,我們舉個例子。Subject / Processor 就是對應異步拉的數據源,也是熱數據源。消費者在訂閱他們后,無論是否可以 request,數據的是否產生以及產生速度也是未知的。
在接受到onNext時:

  • Subject 直接轉發給下游
  • Prosessor 檢查下游的 request 數目,如果少于已經發送的數目,則拋出MissingBackpressureException

因此使用Prosessor稍有不慎就會出錯哦。當然在實際使用中Flowable.subscribe()時,內置的 Subscriber 通常都會在 onSubscribe 時直接向生產者request(Long.MAX_VALUE),在 RxJava2 Long.MAX_VALUE 是一個特殊值,意味著無限流。大家可參見 subscribers

選擇

對于 Observable 與 Flowable 的選擇官方也有提示:

  • 選擇 Observable:

    • 處理不超過 1000 個數據時,因為數據很少,一般不會觸發 OOM
    • 處理 GUI 或者點擊事件時,因為這些事件是異步推的,很難被 backpressured 也一般不這樣做
    • 數據源本質上是同步的,但是平臺不支持 Java Stream API 或者你想用 RxJava 豐富的操作符,因為 Observable 比 Flowable 的性能更好
  • 選擇 Flowable:

    • 處理 10k+ 數據且數據源是可控的
    • 基于拉的且阻塞的數據源

拆分之爭

事實上 RxJava 自 16年 開始社區一度有討論是否要把 RxJava2 拆分成多個庫,因為:

  • 區分是否支持 Backpressure
  • 將通用的代碼獨立出去,如 SchedulerSimpleQueue
  • 減少包體積的大小

但是最終經過討論后還是放棄了:

  • 使用 Proguard 來壓縮 RxJava2 的效果非常好,基本上是用多少保留多少代碼
  • 如果拆分開,強行逼迫使用者需要了解 Backpressure 的概念,且增加了使用方的麻煩,因為他們需要區分自己到底需要哪些庫
  • 多個不同版本子庫的組合可能會導致兼容問題
  • 拆分后,Observable 和 Flowable 互不依賴,互轉需要使用靜態方法,打斷了鏈式操作
  • ...

結語

如果您作為一個 Android 開發者,正在糾結于 RxJava 帶來的好處和他的龐大的體積,那么您可以打消這個顧慮了,只要正確地配置了 Proguard,RxJava 對您包體積大小的影響微乎其微。反過來說,如果沒有配置 Proguard,那么是否引入 RxJava 確實是值得思考的一件事。

事實上,做一個 Android 開發者,筆者認為 RxJava 簡直是為 Android 而生的,天生響應式的事件與 RxJava 的結合能大幅提供您的工作效率。前提是您有一些函數式編程的思維,能把流程拆解成一個個操作符。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372