[譯]使用MVI打造響應式APP(六):恢復狀態

卻把清梅嗅發表於2019-03-20

原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART6 - RESTORING STATE
作者:Hannes Dorfmann
譯者:卻把清梅嗅

在前幾篇文章中,我們討論了Model-View-Intent(MVI)和單向資料流的重要性,這極大簡化了狀態的恢復,那麼其過程和原理是什麼呢,本文我們針對這個問題進行探討。

我們將針對2個場景進行探討:

  • 在記憶體中恢復狀態(比如當螢幕方向發生改變)
  • 持久化恢復狀態(比如從Bundle中獲取之前在Activity.onSaveInstanceState()儲存的狀態)

記憶體中

這種情況處理起來非常簡單。我們只需要保持我們的RxJava流隨著時間的推移從Android生命週期元件(即ActivityFragment甚至ViewGroups)種發射新的狀態。

比如MosbyMviBasePresenter 類在內部就使用了類似這樣的RxJava的流:使用 PublishSubject 發射intent,以及使用 BehaviorSubjectView進行渲染。對此,在 第二部分 中我已經闡述了是如何實現的。其主要思想是MviBasePresenter是一個和View生命週期隔離的元件,因此它能夠被View脫離和附著。在Mosby中,當View被永久銷燬時,Presenterdestroyed(垃圾收集)。同樣,這只是Mosby的一個實現細節,您的MVI實現可能完全不同。

重要的是,像Presenter這樣的元件存活在View的生命週期之外,因為這樣很容易處理View脫離和附著的事件。每當View(重新)依附到Presenter時,我們只需呼叫view.render(previousState)(因此Mosby內部使用了BehaviorSubject)。

這只是處理螢幕方向改變的一種處理方案,它同樣適用於返回棧導航中。例如,Fragment在返回棧中,我們如果從返回棧中返回,我們可以簡單的再次呼叫view.render(previousState),並且,view也會顯示正確的狀態。

事實上,即使沒有View對其進行依附,狀態也依然會被更新,因為Presenter存活在View的生命週期之外,並被儲存在RxJava流中。設想如果沒有View附著,則會收到一個更改資料(部分狀態)的推送通知,同樣,每當View重新附著時,最新狀態(包含來自推送通知的更新資料)將被移交給View進行渲染。

持久化狀態

這種場景在MVI這種單向資料流模式下也很簡單。現在我們希望View層的狀態不僅僅存在於記憶體中,即使程式終止也能夠對其持有。Android中通常的一種解決方案是通過呼叫Activity.onSaveInstanceState(Bundle)去儲存狀態。

MVPMVVM不同的是,在MVI中你持有了代表狀態的Model,View有一個render(state)方法來記錄最新的狀態,這讓持有最後一個狀態變得簡單。因此,顯然易見的是打包和儲存狀態到一個bundle下面,並且之後恢復它:

class MyActivity extends Activity implements MyView {

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

  @Override
  public void render(MyState state) {
    lastState = state;
    ... // 更新UI控制元件
  }

  @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(initialState), 我們讓初始狀態的邏輯下沉到狀態管理的地方: 狀態摺疊器(請參考第三部分),我們將它與.scan(initialState,reducerFunction)搭配使用。

結語

與其他模式相比,使用單向資料流和表示狀態的Model,許多與狀態相關的東西更容易實現。但是,我通常不會在我的App中將狀態持久化,兩個原因:首先,Bundle有大小限制,因此你不能將任意大的狀態放入bundle中(或者你可以將狀態儲存到檔案或像Realm這樣的物件儲存中);其次,我們只討論瞭如何序列化和反序列化狀態,但這不一定與恢復狀態相同。

例如:假設我們有一個LCE(載入內容錯誤)檢視,它會在載入資料時顯示一個指示器,並在完成載入後顯示條目列表,因此狀態就類似MyViewState.LOADING。讓我們假設載入需要一些時間,而就在此時程式剛好被終止了(比如突然一個電話打了進來,導致電話應用佔據了前臺)。

如果我們僅僅將MyViewState.LOADING進行序列化並在之後進行反序列化操作對狀態進行恢復,我們的狀態摺疊器會呼叫view.render(MyViewState.LOADING),目前為止這是正確的,但實際上我們 永遠不會通過這個狀態對網路進行請求載入資料

如您所見,序列化和反序列化狀態與狀態恢復不同,這可能需要一些額外的步驟來增加複雜性(當然對於MVI來說這實現起來同樣比其它模式更簡單),當重新建立View時,包含某些資料的反序列化狀態可能會過時,因此您可能必須重新整理(載入資料)。

在我研究過的大多數應用程式中,我發現相比之下這種方案更簡單且友好:即,將狀態僅僅儲存在記憶體中,並且在程式死亡後以空的初始狀態啟動,好像應用程式將首次啟動一樣。理想情況下,App具有對快取和離線的支援,因此在程式終止後載入資料的速度也會很快。

這最終導致了我和其它Android開發者針對一個問題進行了激烈的辯論:

如果我使用快取或儲存,我已經擁有了一個存活於Android元件生命週期之外的元件,而且我不再需要去處理相關的狀態儲存問題,並且MVI毫無意義,對嗎?

這其中大多數Android開發者推薦 Mike Nakhimovich 發表的 《Presenter 不是為了持久化》這篇文章介紹的 NyTimes Store,這是一個資料載入和快取庫。遺憾的是,那些開發人員不明白 載入資料和快取不是狀態管理。例如,如果我必須從快取或儲存中載入資料呢?

最後,類似NyTimes Store的庫幫助我們處理程式終止了嗎?顯然沒有,因為程式隨時都有可能被終止。我們能做的僅僅是祈禱Android作業系統不要殺死我們的程式,因為我們還有一些需要通過Service做的事(這也是一個能夠不生存於其它android元件生命週期的元件),或者我們可以通過使用RxJava而不再需要Android Service了,這可行嗎?

我們將在下一章節探討關於android servicesRxJava以及MVI,敬請期待。

劇透:我認為我們確實需要服務。

系列目錄

《使用MVI打造響應式APP》原文

《使用MVI打造響應式APP》譯文

《使用MVI打造響應式APP》實戰


關於我

Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的部落格或者Github

如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?

相關文章