原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART6 - RESTORING STATE
作者:Hannes Dorfmann
譯者:卻把清梅嗅
在前幾篇文章中课幕,我們討論了Model-View-Intent(MVI)
和單向數(shù)據(jù)流的重要性宪哩,這極大簡化了狀態(tài)的恢復,那么其過程和原理是什么呢,本文我們針對這個問題進行探討。
我們將針對2個場景進行探討:
- 在內存中恢復狀態(tài)(比如當屏幕方向發(fā)生改變)
- 持久化恢復狀態(tài)(比如從
Bundle
中獲取之前在Activity.onSaveInstanceState()
保存的狀態(tài))
內存中
這種情況處理起來非常簡單。我們只需要保持我們的RxJava
流隨著時間的推移從Android
生命周期組件(即Activity
咳短,Fragment
甚至ViewGroups
)種發(fā)射新的狀態(tài)。
比如Mosby
的 MviBasePresenter
類在內部就使用了類似這樣的RxJava
的流:使用 PublishSubject 發(fā)射intent
蛛淋,以及使用 BehaviorSubject 對View
進行渲染咙好。對此,在 第二部分 中我已經闡述了是如何實現(xiàn)的褐荷。其主要思想是MviBasePresenter
是一個和View
生命周期隔離的組件勾效,因此它能夠被View
脫離和附著。在Mosby
中叛甫,當View
被永久銷毀時层宫,Presenter
被destroyed
(垃圾收集)。同樣其监,這只是Mosby
的一個實現(xiàn)細節(jié)萌腿,您的MVI實現(xiàn)可能完全不同。
重要的是棠赛,像Presenter
這樣的組件存活在View
的生命周期之外哮奇,因為這樣很容易處理View
脫離和附著的事件。每當View
(重新)依附到Presenter
時睛约,我們只需調用view.render(previousState)
(因此Mosby內部使用了BehaviorSubject
)鼎俘。
這只是處理屏幕方向改變的一種處理方案,它同樣適用于返回棧導航中辩涝。例如贸伐,Fragment
在返回棧中,我們如果從返回棧中返回怔揩,我們可以簡單的再次調用view.render(previousState)
捉邢,并且,view
也會顯示正確的狀態(tài)商膊。
事實上伏伐,即使沒有View
對其進行依附,狀態(tài)也依然會被更新晕拆,因為Presenter
存活在View
的生命周期之外藐翎,并被保存在RxJava
流中。設想如果沒有View
附著实幕,則會收到一個更改數(shù)據(jù)(部分狀態(tài))的推送通知吝镣,同樣,每當View
重新附著時昆庇,最新狀態(tài)(包含來自推送通知的更新數(shù)據(jù))將被移交給View
進行渲染末贾。
持久化狀態(tài)
這種場景在MVI
這種單向數(shù)據(jù)流模式下也很簡單。現(xiàn)在我們希望View
層的狀態(tài)不僅僅存在于內存中整吆,即使進程終止也能夠對其持有拱撵。Android
中通常的一種解決方案是通過調用Activity.onSaveInstanceState(Bundle)
去保存狀態(tài)。
與MVP
掂为、MVVM
不同的是裕膀,在MVI
中你持有了代表狀態(tài)的Model
,View
有一個render(state)
方法來記錄最新的狀態(tài),這讓持有最后一個狀態(tài)變得簡單勇哗。因此昼扛,顯然易見的是打包和存儲狀態(tài)到一個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)
, 我們讓初始狀態(tài)的邏輯下沉到狀態(tài)管理的地方: 狀態(tài)折疊器(請參考第三部分),我們將它與.scan(initialState,reducerFunction)
搭配使用扰法。
結語
與其他模式相比蛹含,使用單向數(shù)據(jù)流和表示狀態(tài)的Model
,許多與狀態(tài)相關的東西更容易實現(xiàn)塞颁。但是浦箱,我通常不會在我的App
中將狀態(tài)持久化吸耿,兩個原因:首先,Bundle
有大小限制酷窥,因此你不能將任意大的狀態(tài)放入bundle
中(或者你可以將狀態(tài)保存到文件或像Realm
這樣的對象存儲中)咽安;其次,我們只討論了如何序列化和反序列化狀態(tài)蓬推,但這不一定與恢復狀態(tài)相同妆棒。
例如:假設我們有一個LCE
(加載內容錯誤)視圖,它會在加載數(shù)據(jù)時顯示一個指示器沸伏,并在完成加載后顯示條目列表糕珊,因此狀態(tài)就類似MyViewState.LOADING
。讓我們假設加載需要一些時間毅糟,而就在此時進程剛好被終止了(比如突然一個電話打了進來红选,導致電話應用占據(jù)了前臺)。
如果我們僅僅將MyViewState.LOADING
進行序列化并在之后進行反序列化操作對狀態(tài)進行恢復姆另,我們的狀態(tài)折疊器會調用view.render(MyViewState.LOADING)
纠脾,目前為止這是正確的,但實際上我們 永遠不會通過這個狀態(tài)對網絡進行請求加載數(shù)據(jù)蜕青。
如您所見苟蹈,序列化和反序列化狀態(tài)與狀態(tài)恢復不同,這可能需要一些額外的步驟來增加復雜性(當然對于MVI
來說這實現(xiàn)起來同樣比其它模式更簡單)右核,當重新創(chuàng)建View
時慧脱,包含某些數(shù)據(jù)的反序列化狀態(tài)可能會過時,因此您可能必須刷新(加載數(shù)據(jù))贺喝。
在我研究過的大多數(shù)應用程序中菱鸥,我發(fā)現(xiàn)相比之下這種方案更簡單且友好:即,將狀態(tài)僅僅保存在內存中,并且在進程死亡后以空的初始狀態(tài)啟動躏鱼,好像應用程序將首次啟動一樣氮采。理想情況下,App
具有對緩存和離線的支持染苛,因此在進程終止后加載數(shù)據(jù)的速度也會很快鹊漠。
這最終導致了我和其它Android
開發(fā)者針對一個問題進行了激烈的辯論:
如果我使用緩存或存儲,我已經擁有了一個存活于
Android
組件生命周期之外的組件茶行,而且我不再需要去處理相關的狀態(tài)存儲問題躯概,并且MVI
毫無意義,對嗎畔师?
這其中大多數(shù)Android
開發(fā)者推薦 Mike Nakhimovich
發(fā)表的 《Presenter 不是為了持久化》這篇文章介紹的 NyTimes Store,這是一個數(shù)據(jù)加載和緩存庫娶靡。遺憾的是,那些開發(fā)人員不明白 加載數(shù)據(jù)和緩存不是狀態(tài)管理看锉。例如姿锭,如果我必須從緩存或存儲中加載數(shù)據(jù)呢塔鳍?
最后,類似NyTimes Store的庫幫助我們處理進程終止了嗎呻此?顯然沒有献幔,因為進程隨時都有可能被終止。我們能做的僅僅是祈禱Android
操作系統(tǒng)不要殺死我們的進程趾诗,因為我們還有一些需要通過Service
做的事(這也是一個能夠不生存于其它android組件生命周期的組件),或者我們可以通過使用RxJava
而不再需要Android Service
了蹬蚁,這可行嗎恃泪?
我們將在下一章節(jié)探討關于android services
、RxJava
以及MVI
犀斋,敬請期待贝乎。
劇透:我認為我們確實需要服務。
系列目錄
《使用MVI打造響應式APP》原文
《使用MVI打造響應式APP》譯文
《使用MVI打造響應式APP》實戰(zhàn)
關于我
Hello叽粹,我是卻把清梅嗅览效,如果您覺得文章對您有價值,歡迎 ??虫几,也歡迎關注我的博客或者Github锤灿。
如果您覺得文章還差了那么點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢辆脸?