使用RxJava快取Rest請求

nintyuui發表於2021-09-09

最近,我嘗試使用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客戶端填充儲存器的流程,或者從儲存器本身發出的流程。

在任何情況下,擁有一個真理之源將會看起來更加清晰,並且我覺得使用這種方式來處理像預下載、計劃更新以便給使用者呈現最新的資料將更加容易。

 

原文連結:http://www.apkbus.com/blog-705730-60395.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3209/viewspace-2816117/,如需轉載,請註明出處,否則將追究法律責任。

相關文章