一篇不太一樣的RxJava介紹

距離上篇文章已有半年的時間,雖然這期間沒什么輸出,但是還是關注著RxJava和國內一些動向/文章等等,感覺很多人對RxJava還有些許誤會和“錯誤”的理解。所以今天我們從最基礎的開始,來了解一下RxJava。

我們先退回一步,忘了RxJava,討論一個我們Android開發,甚至很多開發都會遇到的非常棘手的問題,異步問題。
舉個例子,我們需要向 MVP/MVVM中的 Model層 取到一組數據,來做我們的首頁展示,比如這樣的:

dribbble.jpg

一個簡單的Dribbble頁面,由于圖片很多,我們不希望在第一次請求就獲得所有的圖片,我們更希望先獲得一些圖片的MetaData,比如url等等。然后在分別異步加載,來實現一個比較好的用戶體驗。這里我們先不考慮RecyclerView加Glide的組合,用簡單的偽代碼來顯示。

我們的 Model 層需要兩個方法,一個是獲取到我們首頁圖片的MetaData,一個是根據MetaData獲取Bitmap。

如果萬物皆可同步,那么代碼非常簡單:

interface Model{
    fun getList() : List<MetaData>

    fun getBitmap(metaData : MetaData) : Bitmap
}

很多同學喜歡RxJava都是因為“鏈式調用”看起來非常舒服,而鏈式調用或者說高階函數或者操作符并不是RxJava的專利,Java8的 Stream API和Kotlin都有相關的操作,比如我們的代碼在kotlin中可以這樣調用

    model.getList()
        .map { model.getBitmap(it) }
        .forEach { showBitMap(it) }

是不是看起來和你們所謂的優雅簡潔的RxJava鏈式調用一樣呢?

但是同步意味著阻塞,而網絡加載Bitmap大家都知道是非常耗時的。為了不阻塞用戶界面(UI線程),我們希望他在后臺異步執行,執行后再輸出到前臺。
所以我們Android中最簡單直接的方法就是加入CallBack,來實現異步通信。我們的代碼就變成這樣

//定義CallBack
interface CallBack<T> {
    fun onSuccess(t:T)

    fun onError(error:Error)
}

interface Model{
    fun getList(callback:CallBack<List<MetaData>>)

    fun getBitmap(metaData:MetaData, callback:Callback<MetaData>)
}

看過很多RxJava教程的同學肯定覺得這里我要講Callback Hell(回調地獄)了,然后開始展示代碼RxJava來解決回調地獄的問題,但如果這樣我這篇文章也沒什么意義了,豈不是和很多入門文章都一樣了?

我們先來看看為什么我們會出現回調地獄?而在同步的時候卻可以保持我們喜歡的“鏈式調用”
我們在同步的時候,我們做的事情可以簡化成這樣:
進入主界面 -> 通過getList方法獲取 List<MetaData> -> 根據list逐一操作獲取bitmap -> 顯示bitmap
可以看到,我們確實是一條鏈,所以很簡單的通過stream api來實現“鏈式調用”

但是異步的時候呢?
進入主界面 -> getList(callback:CallBack<List<MetaData>>)方法將我們的CallBack傳給后臺 -> 等待后臺回調我們的CallBack

重點來了,與同步的不同,我們這里不是直接獲得了我們的List。而是在等待著異步的另一方通知我們。
同步的時候,我們直接拉取數據 :

1.png

而異步的時候,直觀的看我們應該是在“等待”數據,異步對象向我們推送數據。

2.png

3.png

所以在我們的角度,我們是被動的,也就是英語中的reactive ,也就是所謂的響應式

我們回到我們的例子:

同步的時候,我們是這樣的

interface Model{
    fun getList() : List<MetaData>

    fun getBitmap(metaData : MetaData) : Bitmap
}

而異步的時候,我們的方法沒有了返回值,多了個參數,所以不能使用漂亮的“鏈式調用”
這是因為List 本身,就是一種同步的類型。我們每次操作List,都是對List來拉取數據。不信?我們來看下:

大家都知道List并不是最基礎的集合,常用的集合還有HashMap,Set,Table,Vector等等等等。他們都有一個共同的父類: Iterable<T>

interface Iterable<out T> {
    fun iterator(): Iterator<T>
} 

這里的iterator就是迭代器,他是這個樣子的

interface Iterator<out T> {

    fun next(): T
    
    fun hasNext(): Boolean
}

使用的時候也就是我們最麻煩的迭代方式:

    val i = iterator()
    while(i.hasNext()){
        val value = i.next()
    }

所以我們在Java中有了foreach,以及后面的stream api等等語法糖。
這里我們看到了,我們每次確實首先詢問List,有沒有值,如果有我們獲取這個值,如果沒有,跳出循環,對List的操作結束。讀取完畢。

想象一下,如果我們有一種 AsyncList,對他的讀取都是AsyncList來通知我們,然后再和同步的時候一樣使用高階函數比如map/foreach等等該多好。比如

interface Model{
    fun getList() : AsyncList<MetaData>

    fun getBitmap(metaData : MetaData) : Bitmap
}

我們就可以像同步一樣,

        model.getList()
        .map { model.getBitmap(it) }
        .forEach { showBitMap(it) }

現在我們來根據Iterable設計我們的 AsyncList,上面我們知道了Iterable是同步的,是拉取數據,我們需要的AsyncList是異步的,是他推送數據給我們。
我們和List一樣,給所有的異步集合來一個父類,來設計一個AsyncIterable,我們知道Iterable提供Iterator通過我們主動詢問Iteratornext,hasNext等方法我們主動拉取數據。
所以我們的AsyncIterable理論上來說,應該是我們通過注冊AsyncIterator的方式,將我們的AsyncIterator傳遞給AsyncIterable,讓他來通知我們,實現異步和推送數據。
所以我們的AsyncIterable的實現應該是這樣的

interface AsyncIterable<T> {
    fun iterator(iterator : AsyncIterator<T>) 
} 

(看起來好像有點眼熟?)

我們再來設計AsyncIterator,同步的方式兩個方法,一個是hasNext,也就是我們主動詢問iterable接下來之后還有沒有值的過程,如果是異步的方式,這應該是我們的AsyncIterable,來通知我們,他接下來以后還有沒有值。
所以變成了這樣:

    fun hasNext(has : Boolean) 

對的,通過這種類似CallBack的方式,通知我們有沒有值。true就是還有值,一旦接收到false,就代表迭代結束,我們的AsyncIterable已經遍歷完成了。
另一個方法 next() 就是我們來主動詢問,當前的值是什么。所以我們的AsyncIterable就是通過這個方法,來通知我們當前的值是什么,依然還是通過類似CallBack的方式:

    fun onNext(current:T)

(是不是有些眼熟?(手動滑稽))

這里有兩個問題:
第一個問題:我們在這里隱藏了一個錯誤,因為hasNext()方法返回 false的時候不一定是沒有接下來的值了,也有可能是處理當前值的時候出現了某些個錯誤或者異常,這樣他就不能處理接下來的值,這時候我們的app就會崩潰。所以在異步的時候,我們希望我們的AsyncIterable在出錯的時候,可以通知我們他出錯了,我們也就不進行接下來的處理了。所以我們有了:

    fun onError(e:Throwable)

(是不是也有些眼熟?(手動滑稽))

第二個問題,在hasNext方法顯然有些過于多余,因為在同步的時候,我們并不知道他究竟接下來有沒有值,所以我們每次訪問List的時候,要詢問還有沒有接下來的值,我們再進行下一步。而異步的時候,我們的AsyncIterable肯定知道他自己接下來有沒有值了,我們只希望在最后他沒有值的時候通知我們結束了即可,也就是說我們之前的 hasNext(true)都是多余的。我們其實只關心hasNext(true)被調用的時候。所以我們把他簡化成只有最后結束的時候才調用的方法:

    fun onComplete()

這樣,我們有了我們的AsyncIterator

interface AsyncIterator<T> {

    fun onNext(current:T): 
    
    fun onComplete()

    fun onError(e:Throwable)
}

對的,他就是我們RxJava中的 Observer,而我們的 Asynciterable 就對應著我們的Observable。

interface Observable<T> {
    fun subscribe(observer : Observer<T>) 
} 

由此,我給Observable下一個的定義:

Observable 是一組異步數據的集合

對的,他就是一個集合,和List,Set,Vector一樣。他是一組數據,Collection可以包含0,1很多甚至無限個數據。所以Observable也可以包含0,1,n,甚至無限個數據。

當我們在處理Collection出現異常時(比如NullPointerException),我們的程序會崩潰,不會有接下來的處理。所以我們的Observable在收到onError之后,也不會再有數據推送給我們。

Collection可以通過高階函數(High Oroder Function)進行組合,變換等等,所以作為集合之一的Observable也可以進行組合,變換。

對Iterable進行操作,我們是通過getIterator方法,來獲得Iterator來進行主動詢問,拉取數據實現迭代。
對Observable進行操作,我們是通過subscribe方法,來注冊Observer來進行被動獲取數據,由Obseravble來推送數據,實現迭代。

我們費了這么大力氣,終于抽象出來一個異步的集合。那么他的好處是什么呢?

  1. 首先,這種推送數據的方式才是我們直觀的,異步操作方法,我們在上文了解了,異步操作的時候,作為接收方。我們是被動的,我們沒辦法詢問生產方到底有沒有完成異步任務。只有生產方自己才知道他有沒有完成生產,所以他在完成生產后通知我們,并把結果交給我們這是一種直觀的解決方案。而Java或者其他高級語言沒有提供這一方案。我們才自定義CallBack來實現回調。

  2. 在使用CallBack方案的時候,你知道的信息太多了。舉個例子,我們上文中的

    fun getList(callback:CallBack<List<MetaData>>)

這個方法。我們通過callback知道了,這應該是一個異步操作。可能是耗時的,所以我們可能需要一個線程來執行他,執行之后,他又會給我一個List<MetaData>,而這個list卻又是同步的。你需要關心的事情太多了。
俗話說,把握現在 展望未來!
我們能處理好現在的事情就已經很不錯了,Observable則解決了這一問題。我們上面的方法改完之后應該是這樣的

    fun getList() : Observable<List<MetaData>>

最正確的可能應該是這樣的:

    fun getList() : Observable<MetaData>

對的,因為Observable本身就是個集合,無需再和同步的List嵌套使用。但是由于服務器設計原因, Observable<List<T>>這種使用方式還是很常見的。
在Observable我們無需關心這個方法究竟是怎么生成的。我們像往常一樣迭代數據,我們只需要知道,他生產出數據之后,會通知我即可。
至于你到底怎么生產數據給我?在什么線程?是同步的異步的?有沒有阻塞?

I don't really care!

  1. 操作符,對的因為Observable是一個數學上的集合。集合就可以進行一系列的變換,通過我們定義的高階函數,比如map,filter等等。這些操作符不是RxJava的專利,他是我們對集合的一些常見操作。我們對List,Vector等等也應該可以進行這些操作,而Java本身沒有提供這些。在Java 8后通過stream API補充了這些方法,而RxJava的一大優勢就是不僅僅提供了這個異步的集合類Observable。還提供了上百個常用的操作符。

總結

通過這篇文章,我的目的是讓你理解究竟什么是Observable,為什么Observable是這么設計的,好處是什么,解決了什么問題。
而答案也很明顯。
Observable是一組異步數據的集合,因為異步操作和同步操作有著本質上的區別(推送數據和拉取數據)所以我們根據iterable反過來設計observable。
好處是保持了數學上的集合定義,擺脫了Callback,通過操作符(高階函數)可以對集合實現一些變換操作。解決了通常情況異步操作不直觀,復雜,回調地獄等等問題。

參考文獻(部分鏈接可能需要梯子)

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

推薦閱讀更多精彩內容