假設我需要從網路上查詢一些資料。顯然我可以在每次需要時都從網路上查詢,但是將資料快取在硬碟和記憶體中將更加高效。
具體點說,我想要這麼做:
1.只有在需要從伺服器更新資料的時候,才通過網路查詢。
2.其他情況則從通過快取之前查詢的資料,實現快速的讀取。
接下來,我將通過使用 RxJava 來實現上面的想法。
基本模式
為每一個資料來源(網路、磁碟和記憶體)準備一個Observable<Data> ,然後我們就可以通過使用concat() 和 first() 這兩種操作來構建一個簡單的解決方案。
concat() 可以把多個Observables的結果連結起來。而first() 可以取出一個序列中的第一項。因此,你可以使用concat().first()來取出多個資料來源所組成序列的第一項。
讓我們通過程式碼來看一下:
1 2 3 4 5 6 7 8 9 |
// Our sources (left as an exercise for the reader) Observable<Data> memory = ...; Observable<Data> disk = ...; Observable<Data> network = ...; // Retrieve the first source with data Observable<Data> source = Observable .concat(memory, disk, network) .first(); |
這個模式的關鍵點在於,concat()只會在需要時才去訂閱每個Observable。如果資料已經被快取了,first() 就會停止查詢,即不再去較慢的資料來源中查詢。換種更具體的說法,如果記憶體中返回了資料結果,我們就不必再去磁碟或者網路中查詢了。相反,如果記憶體和磁碟都不返回結果,就會觸發一次新的網路請求。
請注意,在concat()中不同Observable的放置順序是非常重要的,因為在查詢時,是按照指定的順序進行的。
資料儲存
接下來,我們需要對查詢到的資料進行儲存。如果你不把網路查詢的資料結果儲存到磁碟,也不把磁碟查詢的結果快取到記憶體中,那之前的工作就白瞎啦!我們在上面寫的程式碼會一直通過網路查詢資料。
我的解決方案是:每當一個資料來源返回結果,就把該結果儲存或者快取。
1 2 3 4 5 6 7 8 |
Observable<Data> networkWithSave = network.doOnNext(data -> { saveToDisk(data); cacheInMemory(data); }); Observable<Data> diskWithCache = disk.doOnNext(data -> { cacheInMemory(data); }); |
現在,當你使用networkWithSave 和 diskWithCache載入資料時,這些資料都會同時被儲存起來。
採用這種策略還有一個好處,networkWithSave/diskWithCache 可以在任何地方使用,而不僅限在我們的多資料來源模式中。
資料陳舊
不幸的是,我們目前的資料儲存方案效率有點高的過頭了。每次都會返回相同的資料,而這些資料都不知道是幾年前的了。注意,在伺服器更新資料後,我們是需要通過網路查詢新資料的。
這個問題的解決辦法存在於first()中,可以通過它來進行過濾操作,即拒絕返回沒有價值的資料。
1 2 3 |
Observable<Data> source = Observable .concat(memory, diskWithCache, networkWithSave) .first(data -> data.isUpToDate());<br> |
這樣一來,我們就只會得到序列中最快且最新的資料。如果某個資料來源中的資料不是最新的,那麼就會在它後面的資料來源中查詢,直到找到最新的資料。
first() vs. takeFirst()
除了使用first(),我們還有一種選擇,即takeFirst()。
兩種方法間的區別是當所有資料來源都無法返回有效資料時,first()會丟擲NoSuchElementException() 異常,而takeFirst()會正常結束,不丟擲異常。
至於具體使用哪一個方法,主要取決於你是否需要顯式地處理資料的缺失。
程式碼樣例
下面的連結是上述程式碼的完整實現:https://github.com/dlew/rxjava-multiple-sources-sample
如果你想要學習真實的案例,可以進入the Gfycat app , 在這裡使用了文章中介紹的模式以獲取Gfycat的後設資料。所涉及的程式碼並沒有用到文章中介紹的所有能力(因為並不需要),但是它向我們展示了concat().first()的基本使用。