距離上篇文章已有半年的時間,雖然這期間沒什么輸出,但是還是關注著RxJava和國內一些動向/文章等等,感覺很多人對RxJava還有些許誤會和“錯誤”的理解。所以今天我們從最基礎的開始,來了解一下RxJava。
我們先退回一步,忘了RxJava,討論一個我們Android開發,甚至很多開發都會遇到的非常棘手的問題,異步問題。
舉個例子,我們需要向 MVP/MVVM中的 Model層
取到一組數據,來做我們的首頁展示,比如這樣的:
一個簡單的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。而是在等待著異步的另一方通知我們。
同步的時候,我們直接拉取數據 :
而異步的時候,直觀的看我們應該是在“等待”數據,異步對象向我們推送數據。
所以在我們的角度,我們是被動的,也就是英語中的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
通過我們主動詢問Iterator
的next
,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
來推送數據,實現迭代。
我們費了這么大力氣,終于抽象出來一個異步的集合。那么他的好處是什么呢?
首先,這種推送數據的方式才是我們直觀的,異步操作方法,我們在上文了解了,異步操作的時候,作為接收方。我們是被動的,我們沒辦法詢問生產方到底有沒有完成異步任務。只有生產方自己才知道他有沒有完成生產,所以他在完成生產后通知我們,并把結果交給我們這是一種直觀的解決方案。而Java或者其他高級語言沒有提供這一方案。我們才自定義CallBack來實現回調。
在使用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!
- 操作符,對的因為Observable是一個數學上的集合。集合就可以進行一系列的變換,通過我們定義的高階函數,比如map,filter等等。這些操作符不是RxJava的專利,他是我們對集合的一些常見操作。我們對List,Vector等等也應該可以進行這些操作,而Java本身沒有提供這些。在Java 8后通過stream API補充了這些方法,而RxJava的一大優勢就是不僅僅提供了這個異步的集合類Observable。還提供了上百個常用的操作符。
總結
通過這篇文章,我的目的是讓你理解究竟什么是Observable,為什么Observable是這么設計的,好處是什么,解決了什么問題。
而答案也很明顯。
Observable是一組異步數據的集合,因為異步操作和同步操作有著本質上的區別(推送數據和拉取數據)所以我們根據iterable反過來設計observable。
好處是保持了數學上的集合定義,擺脫了Callback,通過操作符(高階函數)可以對集合實現一些變換操作。解決了通常情況異步操作不直觀,復雜,回調地獄等等問題。