使用 MVI 編寫響應式 APP — 第六部分 — 狀態恢復

使用 MVI 編寫響應式 APP—第六部分—狀態恢復

在前面博客中,咱們討論了 Model-View-Intent (MVI)和單項數據流的重要性。這極大的簡化了狀態恢復。這如何作到和爲何可以作到咧?咱們將在這篇博客討論。前端

咱們在這篇博客中將要關注兩種場景: 在內存中恢復狀態(例如屏幕的方向發生改變)和恢復一個「持續狀態」(從先前存儲在 Activity.onSaveInstanceState() 的 Bundle 中恢復)。java

在內存中

這是一個簡單的狀況。咱們只須要讓咱們的 RxJava 隨着時間的變化聽從安卓組件的生命週期(例如,Acitivity,Fragment 甚至是 ViewGroups)繼續發射新的數據。例如,Mosby(做者寫的一個庫) 的 MviBasePresenter 創建在一個 RxJava 流內部經過使用 PublishSubject 來管理 view 的意圖,和經過 BehaviorSubject 去渲染狀態到 view 上。我已經在第二部分結尾處描述這些實現細節。最重要的一點是 MviBasePresenter 是獨立與 view 生命週期的一個組件,所以一個 view 能夠在 Presenter 中被分離和附着。在 Mosby 中只有當 view 永久銷燬 Presenter 纔會被「摧毀」(垃圾回收)。這僅僅是 Mosby 的實現細節。你的 MVI 實現可能和這個徹底不同。最重要的是這種組件好比 Presenter 須要獨立於 view 的生命週期。由於這樣它可以簡單的處理 view 的附着和分離事件,不管什麼時候 view 須要從新附着到 Presenter 咱們只需簡單地調用 view.render(previousState) (所以 Mosby 用內部 BehaviorSubject 來處理)。這僅僅是如何解決屏幕方向的一種解決方案。它也能夠工做在返回棧導航中,例如,Fragment 在返回棧中,咱們若是從返回棧中返回,咱們能夠簡單的再次調用 view.render(previousState),而且,view 也會顯示正確的狀態。 事實上,狀態就算沒有 view 附着也能夠被改變。由於 Presenter 的獨立於生命週期,而且保持 RxJava 狀態流在內存中。想象接收一個改變數據(狀態的一部分)的通知,沒有 view 附着。不管什麼時候 view 被從新附着,最後的狀態(包括從通知中更新的數據)都會交給 view 去渲染。android

持久化狀態

這種場景在 MVI 這種單向數據流模式下也很簡單。假設咱們但願 View 的狀態 (好比 Activity)不只存活在內存中,還能在進程死亡後被暫存。一般,在安卓中咱們使用 Activity.onSaveInstanceState(Bundle) 來保存那樣的狀態。在 MVP 或者 MVVM 中,你不須要使用 Model 來表明狀態(見 第一部分),與之不一樣的是, 在 MVI 中,View 有一個 render(state) 方法來記錄最新的狀態,這讓保持最後一個狀態變得容易。所以,顯然易見的是打包和存儲狀態到一個 bundle 下面,而且過後恢復它,例如:ios

class MyActivity extends Activity implements MyView {

  private final static KEY_STATE = "MyStateKey";
  private MyViewState lastState;

  @Override
  public void render(MyState state) {
    lastState = state;
    ... // update UI widgets
  }

  @Override
  public void onSaveInstanceState(Bundle out){
    out.putParcelable(KEY_STATE, lastState);
  }

  @Override
  public void onCreate(Bundle saved){
    super.onCreate(saved);
    MyViewState initialState = null;
    if (saved != null){
      initialState = saved.getParcelable(KEY_STATE);
    }

    presenter = new MyPresenter( new MyStateReducer(initialState) ); // With dagger: new MyDaggerModule(initialState)
  }
  ...
}
複製代碼

我知道你已經掌握了要點。請注意在 onCreate() 方法中咱們不能直接調用 view.render(state),取而代之,咱們應該讓初始化狀態下沉到狀態管理的地方:狀態摺疊器(看第三部分)在這裏咱們用 .scan(initialState,reducerFunction)git

結論

隨着單向數據流和一個 Model 表明一種狀態,不少與狀態相關的事情,變得相對於其餘的模式更加簡單。然而,一般在個人 APP 中,我不會持久化狀態到 bundle 有如下兩點緣由:第一, Bundle 有大小限制,所以你不能存很大的狀態在 bundle 中(相反,你須要存儲狀態到文件,或者,存儲到對象存儲例如 Realm)。第二,咱們僅僅討論瞭如何去序列化和反序列化,可是,這不必定與恢復狀態相同。github

例子:讓咱們假設咱們有一個 LCE(Loading-Content-Error) 的 view,這個 view 在加載數據時會顯示一個指示器,而且當數據加載完成後會展現一個列表視圖。。所以,這個狀態應當是 MyViewState.LOADING。讓咱們假設加載須要消耗必定的時間,就在加載時候,Activity 進程也被殺掉了(例如,由於其餘應用程序佔據了前臺,像電話 app 由於女票的電話而佔據了前臺)。若是如以前所述,咱們僅僅只是序列化了 MyViewState.LOADING 這個狀態,並在 Activity 被從新建立時反序列化它,那麼咱們的狀態摺疊器只會去調用 view.render(MyViewState.LOADING) 。注意到目前爲止一切都還好,可是接下來咱們會發現去加載數據自己這個操做(發起一次 http 請求)永遠不會執行。這就是盲目簡單地反序列化帶來的結果。後端

正如你所見, 序列化與反序列化狀態,並不一樣於狀態恢復。狀態恢復也許須要一些添加額外的一些會增長複雜性的步驟(使用 MVI 來實現比我目前爲止所使用的任何其餘架構更加簡單)。當 view 被從新建立的時候,反序列化狀態也許包含了一些過時數據。所以,你須要想盡辦法更新數據。在大多數 app 中,我經過努力找到了一種更簡單和更加友好的方法,僅僅保持狀態到內存中。而且當進程死亡的時候,開啓一個空的初始化狀態就像 app 第一次啓動同樣。理想狀況下一個 app 有緩存和離線支持,所以當進程死亡,從新加載數據是很快的。緩存

這最終致使我與其餘安卓開發者都爭論過一個問題:若是我使用了緩存或者存儲,那麼我就已經擁有了一個獨立於安卓生命週期以外的組件,我也再也不須要去處理相關的狀態緩存問題,MVI 徹底就是在胡說嘛!對麼? 大多數這些安卓開發者推薦 Mike Nakhimovich 發表的Presenter 不是爲了持久化這篇文章介紹的 NyTimes Store,一個數據加載和緩存庫。不幸的是,這些這些開發者不理解加載數據和緩存不是狀態管理。例如,若是我不得不從兩個緩存或存儲中加載數據呢?bash

最後,像 NyTimes 緩衝庫幫助咱們處理進程死亡了麼?很顯然沒有,由於進程死亡隨時可能發生。爲來解決這個問題,咱們能作的僅僅是乞求安卓操做系統不要殺死咱們的 app 進程,由於咱們依舊須要作一些工做經過安卓的 service (這個組件也是獨立於其餘安卓組件的生命週期)或者咱們如今用 rxjava 來取代 service,咱們能夠這樣麼?咱們討論關於安卓的 service,rxjava 和 MVI 在下一部分。敬請期待(๑˙ー˙๑)。架構

劇透: 我認爲咱們須要 service。

這篇博客是 "用 MVI 開發響應式App"中的一篇博客。下面是內容表:

這是中文翻譯:


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索