[译]使用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)。

Actually, state can still be updated even if no view is attached because Presenter lives outside of that lifecycle and keeps RxJava state stream alive. Imagine receiving a push notification that changes data (part of state) while no view is attached. Again, whenever view gets reattached the latest state (containing updated data from push notification) is hand over to the view to render.

这只是处理屏幕方向改变的一种处理方案,它同样适用于返回栈导航中。例如,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下面,并且之后恢复它:

扫描二维码关注公众号,回复: 5607437 查看本文章
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

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

猜你喜欢

转载自juejin.im/post/5c92cce0e51d451b893ff7b6