[译]使用MVI打造响应式APP(六):恢复状态

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

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容