使用RxJava快取Rest請求
最近,我嘗試使用RxJava開發了一款閒時備份app。我必須承認,一旦你get到了正確的方式,RxJava幾乎感覺就像作弊。一切看起來更簡潔,多個請求能夠被組合,且非常容易控制。透過在UI執行緒觀察和在其他執行緒訂閱的方式,能夠透過嚴格模式的檢測,而且,你能瞭解到所有最酷的好東西就是在Android上使用RxJava。我不能夠很容易發現的是,如何儲存我的請求的結果,確保即使沒有網路連線時,能夠為使用者呈現快取的內容,同時還是使用Reactive的方式處理一切事情。
快取vs未快取
直接從Rest獲取結果顯示在UI上在很多情況下是合適的,比如當要顯示一個引數不可預測的搜尋結果的時候(想想Ebay,或者亞馬遜,使用者每次查詢的東西都是不一樣的)。
可是有一些情況,顯示之前獲取到的結果可以顯著地提高使用者體驗(相比於顯示載入進度條或者空白頁面)。這種情況包括你的Twitter訂閱,一個剛剛在5分鐘之前獲取過資料的本地天氣預報,或者一個指定使用者的github倉庫列表。
這裡你可以看到,一個相同的activity使用快取的版本和不使用快取的版本之間的區別:
出於這個原因,我試圖找出一個簡潔地方式來快取請求的結果,同時保持使用Reactive方式的流程。
儲存器是真理的唯一來源
全部都是reactive
如果我們想要快取資料同時保持在相同的subscription中一切不變,事情變得有點凌亂。請求的結果拋給UI執行緒,並且響應結果也被儲存在儲存器(storage)中。UI也訂閱了從儲存器(storage)獲取資料,它會檢查哪個結果先返回,返回的資料是否過時。
快取
在這個混合使用的情況中,UI僅訂閱儲存器(storage)的資料,並且使用一個外觀類類封裝了儲存器和向儲存器中填充資料的retrofit客戶端的subscription。一旦儲存器中被填充了新資料,UI執行緒將會自動地收到所有改動的通知。
在這種情況下,observable作為一個hot observable,在它被訂閱的第一時間,它發出儲存器中的內容,和其他任何它可能會發生的改變。
口說無憑,讓我們來看下程式碼
下面這些程式碼的一個可以執行的示例可以在找到。為了寫這個例子,我從看起來驅動了99% rest相關示例程式的被濫用的Github api開始。先對Github說聲抱歉。
首先得有一個儲存器。 我封裝了一個 SQLite幫助類(這是我用生成的),它包含了一個。當插入(insert)方法被呼叫時,PublicSubject能夠收到訂閱,並且我們會收到通知。
public class ObservableRepoDb {
private PublishSubject<List<Repo>> mSubject = PublishSubject.create();
private RepoDbHelper mDbHelper;
private List<Repo> getAllReposFromDb() {
List<Repo> repos = new ArrayList<>();
// .. performs the query and fills the result
return repos;
}
public Observable<List<Repo>> getObservable() {
Observable<List<Repo>> firstTimeObservable =
Observable.fromCallable(this::getAllReposFromDb);
return firstTimeObservable.concatWith(mSubject);
}
public void insertRepo(Repo r) {
// ...
// performs the insertion on the SQLite helper
// ...
List<Repo> result = getAllReposFromDb();
mSubject.onNext(result);
}
}
我們現在已經得到拼圖的第一塊:一個能夠被訂閱的儲存器(storage)。使用concat操作是因為我們想在它一被訂閱就將儲存的內容發出去。
接下來是外觀類,在這裡我們能夠得到我們訂閱的資料,且我們能夠開始一個新的更新操作。
public class ObservableGithubRepos {
ObservableRepoDb mDatabase;
private BehaviorSubject<String> mRestSubject;
// ...
public Observable<List<Repo>> getDbObservable() {
return mDatabase.getObservable();
}
public void updateRepo(String userName) {
Observable<List<Repo>> observable = mClient.getRepos(userName);
observable.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(l -> mDatabase.insertRepoList(l));
}
}
需要注意的是一切都是從UI執行緒發生的。這是因為我們打算將訂閱到資料庫的observable作為唯一的資料來源。
現在,假設observable現在是hot,我們不能為了停止我們可能放在那裡的任意進度指示器而監聽聽其的onComplete方法。我們需要的是另一個subject,讓我們必定能夠更新請求,所以下面是新的外觀類:
public class ObservableGithubRepos {
// ...
public Observable<List<Repo>> getDbObservable() {
return mDatabase.getObservable();
}
public Observable<String> updateRepo(String userName) {
BehaviorSubject<String> requestSubject = BehaviorSubject.create();
Observable<List<Repo>> observable = mClient.getRepos(userName);
observable.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(l -> {
mDatabase.insertRepoList(l);
requestSubject.onNext(userName);},
e -> requestSubject.onError(e),
() -> requestSubject.onCompleted());
return requestSubject;
}
}
在UI端(activity或者fragment)我們必須訂閱儲存器來獲取資料,同時也得訂閱請求的observable以停止進度指示器。每次一個更新被請求的時候,發出掛起請求的狀態的一個observable就會被返回。
mObservable = mRepo.getDbObservable();
mProgressObservable = mRepo.getProgressObservable()
mObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(l -> {
mAdapter.updateData(l);
});
Observable<List<Repo>> progressObservable = mRepo.updateRepo("fedepaol");
progressObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {},
e -> { Log.d("RX", "There has been an error");
mSwipeLayout.setRefreshing(false);
},
() -> mSwipeLayout.setRefreshing(false));
請記住DbObservable是一個hot的,所以每次呼叫updateRepo的時候,資料庫將會被查詢結果填充,並且UI接下來將收到通知。
SqlBrite
如果你覺得所有這些封裝看起來是非常費力的,來自Square的多產的夥計寫了一個SqlBrite,它是一個為了和這個相同的目的而編寫的超級通用的資料庫封裝。我保證它更好用,並且比我們自己寫的個人版本更經得起考驗。
結論
我不知道加入這是否是一個使用RxJava的良好的方式。也許我結束這個場景只是因為我對於RxJava沒有100%的信心,而且我在中間加入了一些非Rx的東西以便更好地控制它。由於我們能夠修改從http客戶端填充儲存器的流程,或者從儲存器本身發出的流程。
在任何情況下,擁有一個真理之源將會看起來更加清晰,並且我覺得使用這種方式來處理像預下載、計劃更新以便給使用者呈現最新的資料將更加容易。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3209/viewspace-2816117/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 使用RxJava實現快取RxJava快取
- Android RxJava+Retrofit完美封裝(快取,請求,生命週期管理)AndroidRxJava封裝快取
- ajax請求如何防止快取快取
- 前端快取API請求資料前端快取API
- 處理REST SOE請求REST
- 使用Retrofit+RxJava實現網路請求RxJava
- HTTP請求的快取(Cache)機制HTTP快取
- iOS 網路請求資料快取iOS快取
- RxJava + Retrofit完成網路請求RxJava
- WKWebView的快取策略不支援POST請求!!!WebView快取
- beego rest ful 請求引數為JSON怎麼獲取GoRESTJSON
- 區分http請求狀態碼來理解快取(協商快取和強制快取)HTTP快取
- 分分鐘使用Retrofit+Rxjava實現網路請求RxJava
- 給Retrofit新增離線快取,支援Post請求快取
- Django REST framework API 指南(1):請求DjangoRESTFrameworkAPI
- SpringMVC(2)-Rest請求風格SpringMVCREST
- elasticsearch常用請求介面Rest API示例ElasticsearchRESTAPI
- Retrofit+Rxjava的資料請求RxJava
- shell妙用 —— 發post請求重新整理CDN快取快取
- 非侵入式入侵 —— Web快取汙染與請求走私Web快取
- RxJava2 實戰知識梳理(8) 使用 publish + merge 優化先載入快取,再讀取網路資料的請求過程RxJava優化快取
- Django REST framework的請求與響應DjangoRESTFramework
- RxJava練武場之——Token前置請求RxJava
- 前端巧用localStorage做“快取”,減少HTTP請求次數前端快取HTTP
- 請問在專案中使用快取快取
- Elasticsearch Java Low Level REST Client(執行請求)ElasticsearchJavaRESTclient
- OkHttp、rxJava、Retrofit聯合網路請求(一)HTTPRxJava
- OkHttp、rxJava、Retrofit聯合網路請求(二)HTTPRxJava
- RxJava+Retrofit+Gson實現網路請求RxJava
- 請求淘寶資料解密祕鑰返回302問題,使用yac快取解密快取
- SharePoint REST API - REST請求導航的資料結構RESTAPI求導資料結構
- Swift 掌控Moya的網路請求、資料解析與快取Swift快取
- SharePoint REST API - 一個請求批量操作RESTAPI
- python requests get請求 如何獲取所有請求Python
- Springboot中Rest風格請求對映如何開啟並使用Spring BootREST
- AngularJS中的$http快取以及處理多個$http請求AngularJSHTTP快取
- [Fiddler]使用fiddler獲取http請求返回HTTP
- 使用Python獲取HTTP請求頭資料PythonHTTP