距離上篇文章已有半年的時間,雖然這期間沒什麼輸出,但是還是關注著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
但是非同步的時候呢?
進入主介面 -> getList(callback:CallBack<List
重點來了,與同步的不同,我們這裡不是直接獲得了我們的List。而是在等待著非同步的另一方通知我們。 同步的時候,我們直接拉取資料 :
而非同步的時候,直觀的看我們應該是在“等待”資料,非同步物件向我們推送資料。
所以在我們的角度,我們是被動的,也就是英語中的reactive ,也就是所謂的響應式
我們回到我們的例子:
同步的時候,我們是這樣的
interface Model{
fun getList() : List<MetaData>
fun getBitmap(metaData : MetaData) : Bitmap
}
複製程式碼
而非同步的時候,我們的方法沒有了返回值,多了個引數,所以不能使用漂亮的**“鏈式呼叫”。 這是因為List 本身,就是一種同步的型別。我們每次操作List,都是對List來拉取**資料。不信?我們來看下:
大家都知道List並不是最基礎的集合,常用的集合還有HashMap,Set,Table,Vector等等等等。他們都有一個共同的父類: Iterable
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
fun getList() : Observable<List<MetaData>>
複製程式碼
最正確的可能應該是這樣的:
fun getList() : Observable<MetaData>
複製程式碼
對的,因為Observable本身就是個集合,無需再和同步的List巢狀使用。但是由於伺服器設計原因, Observable<List>這種使用方式還是很常見的。 在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,通過操作符(高階函式)可以對集合實現一些變換操作。解決了通常情況非同步操作不直觀,複雜,回撥地獄等等問題。
####參考文獻(部分連結可能需要梯子)