在系统发起的活动或应用程序销毁过程中,及时维护和恢复活动的UI状态是用户体验的关键部分。在这些情况下,用户希望UI状态保持不变,但是系统已经销毁了Activity和存储在其中的任何状态。
要消除用户期望和系统行为之间的差距,可以使用ViewModel对象、onSaveInstanceState()方法和/或本地存储的组合,以在应用程序和活动实例转换之间持久化UI状态。如何组合这些选项取决于UI数据的复杂性、应用程序的用例,以及检索和内存使用速度的考虑。
无论采用哪种方法,都应该确保应用程序满足用户对其UI状态的期望,并提供一个平滑的、流畅的UI(避免将数据加载到UI中,特别是在频繁发生的配置更改之后,比如旋转)。在大多数情况下,您应该同时使用ViewModel和onSaveInstanceState()。
本文讨论用户对UI状态的期望、保存状态的选项、权衡和限制。
用户的期望和系统的行为
根据用户所采取的操作,他们可以期望活动状态被清除,或者状态被保留。在某些情况下,系统会自动执行用户期望的操作。在其他情况下,系统的功能与用户期望的相反。
用户发起的UI状态解除
用户希望在启动活动时,该活动的临时UI状态将保持不变,直到用户完全取消活动为止。用户可以通过以下方式完全取消活动:
- 按后退按钮
- 从近期任务列表界面上滑动Activity。
- 从活动中导航出去。
- 从设置界面杀死应用程序。
- 完成某种“完成”活动(由Activity.finish()支持)
在这些完全解雇案例中,用户的假设是他们已经永久地离开了这个Activity,如果他们重新打开Activity,他们希望Activity从一个全新的状态开始。这些解雇场景的底层系统行为与用户期望相匹配——Activity实例将被销毁并从内存中删除,以及存储在其中的任何状态以及与该Activity相关联的任何保存的实例状态记录。
关于完全解雇的这条规则有一些例外——例如,用户可能希望浏览器将他们带到他们正在查看的确切网页,然后使用back按钮退出浏览器。
系统发起的UI状态解除
用户期望Activity的UI状态在整个配置更改中保持不变,例如旋转或切换到多窗口模式。但是,默认情况下,当发生这样的配置更改时,系统会破坏Activity,清除存储在活动实例中的任何UI状态。要了解有关设备配置的更多信息,请参Configuration reference page。注意,可以(尽管不建议)覆盖配置更改的默认行为。请参阅Handling the Configuration Change Yourself以获得更多细节。
用户也希望如果他们暂时切换到另一个应用程序,然后再回到你的应用程序时Activity的UI状态保持不变。例如,用户在搜索Activity中执行搜索,然后按home键或接听一个电话——当他们返回到搜索Activity时,他们希望找到搜索关键字,结果仍然和以前一样。
在这个场景中,你的应用程序被放置在后台,系统会尽力将你的应用程序保存在内存中。然而,当用户与其他应用程序交互时,系统可能会破坏应用程序进程。在这种情况下,Activity实例将被销毁,以及存储在其中的任何状态。当用户重新启动该应用程序时,该Activity居然处于一个全新的状态中。要了解关于过程死亡的更多信息,请参见Processes and Application Lifecycle。
保存UI状态的选项
当用户对UI状态的期望与默认系统行为不匹配时,必须保存并恢复用户的UI状态,以确保系统启动的销毁对用户是透明的。
保存UI状态的每个选项都在以下几个方面有所不同,它们影响了用户体验:
这里本是一张表格 googel官方的内容错乱了 待定填入。
使用ViewModel管理 configuration changes
ViewModel管理存储和管理ui相关数据比较理想。它允许快速访问UI数据,并帮助您避免通过旋转、窗口调整和其他常见的配置更改从网络或磁盘重新获取数据。要学习如何实现ViewModel,请参见ViewModel guide。
ViewModel保留了内存中的数据,这意味着从ViewModel中检索数据比从磁盘或网络中获取数据要便宜得多。ViewModel与Activity(或其他生命周期所有者)相关联——它在配置更改期间驻留在内存中,系统会自动将视图模型与新Activity实例关联起来,该Activity实例是由配置更改产生的。
当用户退出Activity或Fragment,或者调用finish()时,ViewModel会自动被系统破坏,这意味着在这些场景中,用户期望的状态将被清除。
与保存的实例状态不同,视图模型在系统启动的进程死亡过程中被销毁。这就是为什么您应该将ViewModel对象与onSaveInstanceState()(或其他磁盘持久性)结合使用,在savedInstanceState中存储标识符,以帮助ViewModel在系统死后重新加载数据。
如果您已经有了一个用于在配置更改中存储UI状态的内存解决方案,那么您可能不需要使用ViewModel。
使用onSaveInstanceState()作为备份来处理系统启动的进程死亡
onSaveInstanceState()回调存储了重新加载UI控制器(Activity 或Fragment)的状态所需的数据,如果系统被销毁了,然后重新创建该控制器。要了解如何实现保存的实例状态,请参阅Activity Lifecycle guide.中的保存和恢复Activity状态。
保存的实例状态包同时保存配置更改和进程死亡,但是由于onSavedInstanceState()将数据序列化到磁盘,因此受到存储和速度的限制。如果序列化的对象是复杂的,序列化可以消耗大量内存。因为这个过程在配置更改期间发生在主线程上,如果时间过长,序列化会导致掉帧和视图卡顿。
不要使用存储onSavedInstanceState()来存储大量数据,比如位图,或者需要长时间序列化或反序列化的复杂数据结构。相反,只存储原始类型和简单的小对象,比如字符串。因此,使用onSaveInstanceState()来存储必要的最小数据量(如ID),以重新创建必要的数据,以便在其他持久性机制失败时,将UI恢复到以前的状态。大多数应用程序应该实现onSaveInstanceState()来处理系统启动的进程死亡。
根据您的应用程序的使用情况,您可能根本不需要使用onSaveInstanceState()。例如,浏览器可能会将用户带回他们正在查看的网页,然后才退出浏览器。如果您的活动行为是这样的,您可以使用onSaveInstanceState(),而不是将所有的东西都保存在本地。
另外,当您从意图中打开一个Activity时,当配置发生变化时,当系统恢复活动时,额外的附加组件将被交付到活动中。如果某个UI状态数据(如搜索查询)在Activity启动时作为额外的意图传入,那么您可以使用额外的bundle而不是onSaveInstanceState() bundle。要了解更多关于意图的附加内容,请看Intent and Intent Filters。
在任何一种情况下,您都应该使用ViewModel,以避免在配置更改期间从数据库中重新加载数据。
在UI数据保持简单和轻量级的情况下,您可以使用onSaveInstanceState()来保存状态数据。
使用本地持久性来处理复杂或大型数据的过程死亡。
持久的本地存储,例如数据库或共享首选项,只要您的应用程序安装在用户的设备上(除非用户为您的应用程序清除了数据),它就会继续存在。虽然这样的本地存储在系统启动的活动和应用程序进程死亡时仍然存在,但是检索起来很昂贵,因为它必须从本地存储读入内存。通常,这种持久的本地存储可能已经成为应用程序体系结构的一部分,用于存储您不想丢失的所有数据,如果您打开和关闭该活动。
ViewModel和save实例状态都不是长期存储解决方案,因此不能替代本地存储,比如数据库。相反,应该使用这些机制临时存储临时UI状态,并为其他应用程序数据使用持久存储。有关如何利用本地存储来持久存储应用程序模型数据的详细信息,请参阅应用程序架构指南(例如,在设备重新启动时)。
UI状态的管理:分而治之
通过将工作划分到各种类型的持久性机制中,您可以有效地保存和恢复UI状态。在大多数情况下,每种机制都应该根据数据复杂性、访问速度和生命周期的权衡,存储活动中使用的不同类型的数据。
- 本地持久性:如果打开并关闭活动,存储所有您不想丢失的数据。
- 示例:一组歌曲对象,可以包括音频文件和元数据。
- ViewModel:存储在内存中的所有数据,以显示相关的UI控制器。
- 示例:最新搜索的歌曲对象和最新的搜索查询。
- onSaveInstanceState():如果系统停止并重新创建UI控制器,则存储少量数据,以便轻松地重新加载活动状态。这里不存储复杂对象,而是在本地存储中保存复杂对象,并在onSaveInstanceState()中存储这些对象的惟一ID。
- 示例:存储最近的搜索查询。
作为一个例子,考虑一个允许你搜索你的歌曲库的活动。下面是如何处理不同的事件:
当用户添加歌曲时,ViewModel会立即委托在本地持久化该数据。如果这个新添加的歌曲应该显示在UI中,那么您也应该更新ViewModel对象中的数据,以反映歌曲的添加。记住要从主线程中删除所有的数据库。
当用户搜索一首歌曲时,你从数据库中加载的UI控制器的复杂歌曲数据应该立即存储在ViewModel对象中。您还应该将搜索查询本身保存在ViewModel对象中。
当活动进入后台时,系统调用onSaveInstanceState()。您应该在onSaveInstanceState()包中保存搜索查询。这少量的数据很容易保存。它也是将活动恢复到当前状态所需的所有信息。
恢复复杂状态:重新组装
当用户返回到活动时,有两个可能的场景来重新创建活Activity:
- Activity在系统停止后重新创建。该Activity将查询保存在onSaveInstanceState()包中,并将查询传递给ViewModel。ViewModel看到它没有搜索结果缓存,并使用给定的搜索查询加载搜索结果。
- 该Activity是在配置更改之后创建的。该Activity将查询保存在onSaveInstanceState()包中,并且ViewModel已经缓存了搜索结果。您将查询从onSaveInstanceState()包传递到ViewModel,它确定它已经加载了必需的数据,并且不需要重新查询数据库。
注意:当一个Activity最初创建时,onSaveInstanceState() bundle不包含任何数据,ViewModel对象为空。当您创建ViewModel对象时,您传递一个空查询,该查询告诉ViewModel对象没有加载数据。因此,活动从空状态开始。