原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART6 - RESTORING STATE
作者:Hannes Dorfmann
譯者:卻把清梅嗅
在前幾篇文章中,我們討論了Model-View-Intent(MVI)
和單向資料流的重要性,這極大簡化了狀態的恢復,那麼其過程和原理是什麼呢,本文我們針對這個問題進行探討。
我們將針對2個場景進行探討:
- 在記憶體中恢復狀態(比如當螢幕方向發生改變)
- 持久化恢復狀態(比如從
Bundle
中獲取之前在Activity.onSaveInstanceState()
儲存的狀態)
記憶體中
這種情況處理起來非常簡單。我們只需要保持我們的RxJava
流隨著時間的推移從Android
生命週期元件(即Activity
,Fragment
甚至ViewGroups
)種發射新的狀態。
比如Mosby
的 MviBasePresenter
類在內部就使用了類似這樣的RxJava
的流:使用 PublishSubject 發射intent
,以及使用 BehaviorSubject 對View
進行渲染。對此,在 第二部分 中我已經闡述了是如何實現的。其主要思想是MviBasePresenter
是一個和View
生命週期隔離的元件,因此它能夠被View
脫離和附著。在Mosby
中,當View
被永久銷燬時,Presenter
被destroyed
(垃圾收集)。同樣,這只是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)
去儲存狀態。
與MVP
、MVVM
不同的是,在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 services
、RxJava
以及MVI
,敬請期待。
劇透:我認為我們確實需要服務。
系列目錄
《使用MVI打造響應式APP》原文
《使用MVI打造響應式APP》譯文
《使用MVI打造響應式APP》實戰
關於我
Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的部落格或者Github。
如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?