We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART6 - RESTORING STATE 作者:Hannes Dorfmann 译者:却把清梅嗅
在前几篇文章中,我们讨论了Model-View-Intent(MVI)和单向数据流的重要性,这极大简化了状态的恢复,那么其过程和原理是什么呢,本文我们针对这个问题进行探讨。
Model-View-Intent(MVI)
我们将针对2个场景进行探讨:
Bundle
Activity.onSaveInstanceState()
这种情况处理起来非常简单。我们只需要保持我们的RxJava流随着时间的推移从Android生命周期组件(即Activity,Fragment甚至ViewGroups)种发射新的状态。
RxJava
Android
Activity
Fragment
ViewGroups
比如Mosby的 MviBasePresenter 类在内部就使用了类似这样的RxJava的流:使用 PublishSubject 发射intent,以及使用 BehaviorSubject 对View进行渲染。对此,在 第二部分 中我已经阐述了是如何实现的。其主要思想是MviBasePresenter是一个和View生命周期隔离的组件,因此它能够被View脱离和附着。在Mosby中,当View被永久销毁时,Presenter被destroyed(垃圾收集)。同样,这只是Mosby的一个实现细节,您的MVI实现可能完全不同。
Mosby
MviBasePresenter
intent
View
Presenter
destroyed
重要的是,像Presenter这样的组件存活在View的生命周期之外,因为这样很容易处理View脱离和附着的事件。每当View(重新)依附到Presenter时,我们只需调用view.render(previousState)(因此Mosby内部使用了BehaviorSubject)。
view.render(previousState)
BehaviorSubject
这只是处理屏幕方向改变的一种处理方案,它同样适用于返回栈导航中。例如,Fragment在返回栈中,我们如果从返回栈中返回,我们可以简单的再次调用view.render(previousState),并且,view也会显示正确的状态。
view
事实上,即使没有View对其进行依附,状态也依然会被更新,因为Presenter存活在View的生命周期之外,并被保存在RxJava流中。设想如果没有View附着,则会收到一个更改数据(部分状态)的推送通知,同样,每当View重新附着时,最新状态(包含来自推送通知的更新数据)将被移交给View进行渲染。
这种场景在MVI这种单向数据流模式下也很简单。现在我们希望View层的状态不仅仅存在于内存中,即使进程终止也能够对其持有。Android中通常的一种解决方案是通过调用Activity.onSaveInstanceState(Bundle)去保存状态。
MVI
Activity.onSaveInstanceState(Bundle)
与MVP、MVVM不同的是,在MVI中你持有了代表状态的Model,View有一个render(state)方法来记录最新的状态,这让持有最后一个状态变得简单。因此,显然易见的是打包和存储状态到一个bundle下面,并且之后恢复它:
MVP
MVVM
Model
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)搭配使用。
onCreate()
view.render(initialState)
.scan(initialState,reducerFunction)
与其他模式相比,使用单向数据流和表示状态的Model,许多与状态相关的东西更容易实现。但是,我通常不会在我的App中将状态持久化,两个原因:首先,Bundle有大小限制,因此你不能将任意大的状态放入bundle中(或者你可以将状态保存到文件或像Realm这样的对象存储中);其次,我们只讨论了如何序列化和反序列化状态,但这不一定与恢复状态相同。
App
Realm
例如:假设我们有一个LCE(加载内容错误)视图,它会在加载数据时显示一个指示器,并在完成加载后显示条目列表,因此状态就类似MyViewState.LOADING。让我们假设加载需要一些时间,而就在此时进程刚好被终止了(比如突然一个电话打了进来,导致电话应用占据了前台)。
LCE
MyViewState.LOADING
如果我们仅仅将MyViewState.LOADING进行序列化并在之后进行反序列化操作对状态进行恢复,我们的状态折叠器会调用view.render(MyViewState.LOADING),目前为止这是正确的,但实际上我们 永远不会通过这个状态对网络进行请求加载数据。
view.render(MyViewState.LOADING)
如您所见,序列化和反序列化状态与状态恢复不同,这可能需要一些额外的步骤来增加复杂性(当然对于MVI来说这实现起来同样比其它模式更简单),当重新创建View时,包含某些数据的反序列化状态可能会过时,因此您可能必须刷新(加载数据)。
在我研究过的大多数应用程序中,我发现相比之下这种方案更简单且友好:即,将状态仅仅保存在内存中,并且在进程死亡后以空的初始状态启动,好像应用程序将首次启动一样。理想情况下,App具有对缓存和离线的支持,因此在进程终止后加载数据的速度也会很快。
这最终导致了我和其它Android开发者针对一个问题进行了激烈的辩论:
如果我使用缓存或存储,我已经拥有了一个存活于Android组件生命周期之外的组件,而且我不再需要去处理相关的状态存储问题,并且MVI毫无意义,对吗?
这其中大多数Android开发者推荐 Mike Nakhimovich 发表的 《Presenter 不是为了持久化》这篇文章介绍的 NyTimes Store,这是一个数据加载和缓存库。遗憾的是,那些开发人员不明白 加载数据和缓存不是状态管理。例如,如果我必须从缓存或存储中加载数据呢?
Mike Nakhimovich
最后,类似NyTimes Store的库帮助我们处理进程终止了吗?显然没有,因为进程随时都有可能被终止。我们能做的仅仅是祈祷Android操作系统不要杀死我们的进程,因为我们还有一些需要通过Service做的事(这也是一个能够不生存于其它android组件生命周期的组件),或者我们可以通过使用RxJava而不再需要Android Service了,这可行吗?
Service
Android Service
我们将在下一章节探讨关于android services、RxJava以及MVI,敬请期待。
android services
《使用MVI打造响应式APP》原文
Part 1: Model Part 2: View and Intent Part 3: State Reducer Part 4: Independent UI Components Part 5: Debugging with ease Part 6: Restoring State Part 7: Timing (SingleLiveEvent problem) Part 8: In-App Navigation
《使用MVI打造响应式APP》译文
[译]使用MVI打造响应式APP(一):Model到底是什么 [译]使用MVI打造响应式APP[二]:View层和Intent层 [译]使用MVI打造响应式APP[三]:状态折叠器 [译]使用MVI打造响应式APP[四]:独立性UI组件 [译]使用MVI打造响应式APP[五]:轻而易举地Debug [译]使用MVI打造响应式APP[六]:恢复状态 [译]使用MVI打造响应式APP[七]:掌握时机(SingleLiveEvent问题) [译]使用MVI打造响应式APP[八]:导航
《使用MVI打造响应式APP》实战
实战:使用MVI打造响应式&函数式的Github客户端
Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
The text was updated successfully, but these errors were encountered:
No branches or pull requests
[译]使用MVI打造响应式APP(六):恢复状态
在前几篇文章中,我们讨论了
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
下面,并且之后恢复它:我想你已得要领,请注意,在
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
开发者推荐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。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
The text was updated successfully, but these errors were encountered: