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

原文: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)。

比如MosbyMviBasePresenter 類在內部就使用了類似這樣的RxJava的流:使用 PublishSubject 發(fā)射intent蛛淋,以及使用 BehaviorSubjectView進行渲染咙好。對此,在 第二部分 中我已經闡述了是如何實現(xiàn)的褐荷。其主要思想是MviBasePresenter是一個和View生命周期隔離的組件勾效,因此它能夠被View脫離和附著。在Mosby中叛甫,當View被永久銷毀時层宫,Presenterdestroyed(垃圾收集)。同樣其监,這只是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 servicesRxJava以及MVI犀斋,敬請期待贝乎。

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

系列目錄

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

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

《使用MVI打造響應式APP》實戰(zhàn)


關于我

Hello叽粹,我是卻把清梅嗅览效,如果您覺得文章對您有價值,歡迎 ??虫几,也歡迎關注我的博客或者Github锤灿。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末但校,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啡氢,更是在濱河造成了極大的恐慌状囱,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倘是,死亡現(xiàn)場離奇詭異亭枷,居然都是意外死亡,警方通過查閱死者的電腦和手機搀崭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進店門叨粘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘤睹,你說我怎么就攤上這事宣鄙。” “怎么了默蚌?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵冻晤,是天一觀的道長。 經常有香客問我绸吸,道長鼻弧,這世上最難降的妖魔是什么设江? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮攘轩,結果婚禮上叉存,老公的妹妹穿的比我還像新娘。我一直安慰自己度帮,他們只是感情好歼捏,可當我...
    茶點故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笨篷,像睡著了一般瞳秽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上率翅,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天练俐,我揣著相機與錄音,去河邊找鬼冕臭。 笑死腺晾,一個胖子當著我的面吹牛,可吹牛的內容都是我干的辜贵。 我是一名探鬼主播悯蝉,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼托慨!你這毒婦竟也來了泉粉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤榴芳,失蹤者是張志新(化名)和其女友劉穎嗡靡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窟感,經...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡讨彼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柿祈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哈误。...
    茶點故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖躏嚎,靈堂內的尸體忽然破棺而出蜜自,到底是詐尸還是另有隱情,我是刑警寧澤卢佣,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布重荠,位于F島的核電站,受9級特大地震影響虚茶,放射性物質發(fā)生泄漏戈鲁。R本人自食惡果不足惜仇参,卻給世界環(huán)境...
    茶點故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婆殿。 院中可真熱鬧诈乒,春花似錦烹棉、人聲如沸桩卵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽消约。三九已至肠鲫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荆陆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工集侯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留被啼,地道東北人。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓棠枉,卻偏偏與公主長得像浓体,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辈讶,可洞房花燭夜當晚...
    茶點故事閱讀 43,576評論 2 349

推薦閱讀更多精彩內容