Jetpack学习10-保存UI状态

[TOC]

保存UI状态

​ 在系统启动的activity或应用程序销毁过程中及时保留和恢复activity的UI状态是用户体验的关键部分。在这些情况下,用户期望UI状态保持不变,但系统会破坏activity以及存储在其中的任何状态。

​ 为了弥补用户期望和系统行为之间的差距,可以使用ViewModel对象、onSaveInstanceState()方法和/或本地存储的组合来跨应用程序和activity实例转换持久化UI状态。决定如何组合这些选项取决于UI数据的复杂性、应用程序的用例以及检索速度和内存使用情况。

​ 无论采用哪种方法,都应该确保应用程序满足用户对UI状态的期望,并提供流畅、快速的UI(避免在将数据加载到UI时的延迟,尤其是在频繁发生配置更改(如旋转)之后)。在大多数情况下,应该同时使用ViewModel和onSaveInstanceState()。

​ 此页面讨论了用户对UI状态的期望,可用于保存状态的选项,每个的权衡和限制。

用户期望和系统行为

​ 根据用户所采取的操作,他们要么希望清除activity状态,要么希望保留状态。在某些情况下,系统会自动执行用户所期望的操作。在其他情况下,系统执行与用户期望相反的操作。

用户启动的UI终止状态

用户期望在他们开始activity时,该activity的UI瞬态状态将保持不变,直到用户完全终止activity为止。用户可以通过以下方式完全终止activity:

  • 按下back按钮
  • 从Overview (Recents)屏幕上删除activity
  • 从activity向上导航
  • 从“设置”屏幕中杀死该应用
  • 完全“完成”activity(由activity .finish()支持)

​ 在这些完全终止案例中,用户的假设是他们已经永久地从activity中导航出去,如果他们重新打开该activity,他们希望该activity从一个干净的状态开始。这些终止场景的底层系统行为符合用户的期望——activity实例将被销毁并从内存中删除,同时删除存储在其中的任何状态以及与activity关联的任何保存的实例状态记录。

​ 这条关于完全删除的规则也有一些例外——例如,用户可能希望浏览器在使用后退按钮退出浏览器之前将他们带到他们正在查看的确切页面。

系统启动的UI终止状态

​ 用户希望activity的UI状态在整个配置更改期间保持不变,比如旋转或切换到多窗口模式。但是,默认情况下,当发生这样的配置更改时,系统会销毁activity,删除存储在activity实例中的任何UI状态。有关设备配置的更多信息,请参见Configuration reference page。注意,可以(虽然不建议)覆盖配置更改的默认行为。有关详细信息,请参见Handling the Configuration Change Yourself

​ 如果用户暂时切换到其他应用程序然后稍后返回您的应用程序,则用户还希望您的activity的UI状态保持不变。例如,用户在您的搜索activity中执行搜索,然后按下主页按钮或接听电话 - 当他们返回搜索activity时,他们希望找到搜索关键字并且结果仍然存在,就像之前一样。

​ 在这种情况下,您的应用程序将放置在后台,系统会尽最大努力将您的应用程序进程保留在内存中。但是,当用户离开与其他应用程序交互时,系统可能会破坏应用程序进程。在这种情况下,activity实例以及存储在其中的任何状态都将被销毁。当用户重新启动应用程序时,activity意外处于干净状态。要了解有关进程死亡的更多信息,请参阅 Processes and Application Lifecycle

用于保留UI状态的选项

当用户对UI状态的期望与默认系统行为不匹配时,您必须保存并恢复用户的UI状态,以确保系统启动的销毁对用户是透明的。

保存UI状态的每个选项都在以下影响用户体验的维度上有所不同:

ViewModel Saved instance state Persistent storage
Storage location in memory serialized to disk on disk or network
Survives configuration change Yes Yes Yes
Survives system-initiated process death No Yes Yes
Survives user complete activity dismissal/onFinish() No No Yes
Data limitations complex objects are fine, but space is limited by available memory only for primitive types and simple, small objects such as String only limited by disk space or cost / time of retrieval from the network resource
Read/write time quick (memory access only) slow (requires serialization/deserialization and disk access) slow (requires disk access or network transaction)

使用ViewModel处理配置更改

​ 当用户频繁使用应用程序时,ViewModel是存储和管理ui相关数据的理想工具。它允许快速访问UI数据,并帮助您避免在旋转,窗口大小调整和其他常见配置更改中从网络或磁盘中重新获取数据。要了解如何实现ViewModel,请参阅ViewModel指南

​ ViewModel将数据保留在内存中,这意味着检索比ViewModel中的数据比磁盘或网络更合算。ViewModel与activity(或其他一些生命周期所有者)相关联 - 它在配置更改期间保留在内存中,系统自动将ViewModel与配置更改产生的新activity实例相关联。

​ 当用户退出activity或fragment或调用finish()时,系统会自动销毁viewmodel,这意味着在这些场景中,状态将如用户所期望的那样被清除。

​ 与保存的实例状态不同,ViewModel在系统启动的进程死亡期间被销毁。这就是为什么你应该将ViewModel对象与onSaveInstanceState()(或其他一些磁盘持久性)结合使用,在savedInstanceState中存储标识符以帮助views models在系统死亡后重新加载数据。

​ 如果您已经在内存中准备好了用于跨配置更改存储UI状态的解决方案,那么您可能不需要使用ViewModel。

使用onSaveInstanceState()作为备份来处理系统启动的进程死亡

​ onSaveInstanceState()回调函数存储了重新加载UI控制器状态(如activity或fragment)所需的数据,如果系统销毁并稍后重新创建该控制器的话。要了解如何实现已保存的实例状态,请参阅Activity Lifecycle guide中的保存和恢复activity状态。

​ 保存实例状态在配置更改和进程终止时,持续存在,但是由于onSavedInstanceState()将数据序列化到磁盘,所以受到存储量和速度的限制。如果序列化的对象比较复杂,那么序列化会消耗大量内存。由于这个过程在配置更改期间发生在主线程上,如果序列化耗时太长,可能会导致帧丢失和视觉卡顿。

​ 不要使用onSavedInstanceState()存储大量数据,如位图,也不要使用需要长时间序列化或反序列化的复杂数据结构。相反,只存储基本类型和简单的小对象,比如String。因此,使用onSaveInstanceState()存储少量必要的数据,例如ID,以便在其他持久性机制失败时重新创建必要的数据,将UI恢复到以前的状态。大多数应用程序应该实现onSaveInstanceState()来处理系统启动的进程死亡。

​ 根据应用程序的使用情况,您可能根本不需要使用onSaveInstanceState()。例如,浏览器可能会将用户带回他们退出浏览器之前正在查看的准确页面。如果您的activity以这种方式运行,您可以放弃使用onSaveInstanceState(),而是在本地保存所有内容。

​ 此外,当您从intent打开一个activity时,the bundle of extras在配置更改和系统恢复该activity时都被交付给该activity。如果在启动activity时将UI状态数据(例如搜索查询)作为intent extra传递进来,那么可以使用extras bundle而不是onSaveInstanceState() bundle。要了解有关intent extras的更多信息,请参阅Intent和Intent Filters

使用本地持久(存储)处理复杂或大型数据来应对进程死亡

只要您的应用程序安装在用户的设备上,持久性本地存储(例如数据库或shared preferences)就会存在(除非用户清除应用程序的数据)。虽然这样的本地存储在系统启动的activity和应用程序进程死亡后仍然存在,但检索起来可能很昂贵,因为它必须从本地存储器读取到内存中。通常,此持久性本地存储可能已经是应用程序体系结构的一部分,用于存储您打开和关闭activity时不想丢失的所有数据。

ViewModel和保存的实例状态都不是长期存储解决方案,因此不能替代本地存储,例如数据库。相反,您应该使用这些机制暂时存储临时UI状态,并将持久存储用于其他应用程序数据。 有关如何利用本地存储长期保留应用程序模型数据(例如,设备重新启动)的更多详细信息,请参阅Guide to App Architecture

管理UI状态:分而治之

通过将工作划分到各种类型的持久性机制中,可以有效地保存和恢复UI状态。在大多数情况下,根据数据复杂性、访问速度和生命周期的权衡,每种机制都应该存储不同类型的数据(在activity中使用的):

  • 本地持久存储:存储在打开和关闭activity时不想丢失的所有数据。
    • 示例:歌曲对象的集合,可包括音频文件和元数据。
  • ViewModel:在内存中存储显示相关的UI控制器所需的所有数据。
    • 示例:最近搜索的歌曲对象和最近的搜索查询。
  • onSaveInstanceState():存储少量数据,以便在系统停止时轻松重新加载activity状态,然后重新创建UI控制器。与其在这里存储复杂对象,不如将复杂对象持久存储在本地存储中,并在onSaveInstanceState()中存储这些对象的惟一ID。
    • 示例:存储最近的搜索查询。

例如,考虑一个允许您搜索歌曲库的activity。以下是处理不同事件的方法:

​ 当用户添加一首歌时,ViewModel立即委托本地存储该数据。如果这个新添加的歌曲应该显示在UI中,那么还应该更新ViewModel对象中的数据,以反映添加的歌曲。记住要在主线程之外执行所有数据库插入。

​ 当用户搜索一首歌时,从数据库中为UI控制器加载的任何复杂歌曲数据都应该立即存储在ViewModel对象中。您还应该将搜索查询本身保存在ViewModel对象中。

​ 当activity进入后台时,系统调用onSaveInstanceState()。您应该将搜索查询保存在onSaveInstanceState()包中。这一小部分数据很容易保存。它也是将activity恢复到当前状态所需的所有信息。

恢复复杂状态:重新组装碎片

当用户返回activity时,有两种可能的场景来重新创建activity:

  • 该activity在系统停止后被重新创建。该activity将查询保存在onSaveInstanceState() bundle中,并应将查询传递给ViewModelViewModel看到它没有缓存搜索结果,并使用给定的搜索查询委托加载搜索结果。
  • 该activity是在配置更改之后创建的。该activity将查询保存在onSaveInstanceState() bundle中,并且ViewModel已经缓存了搜索结果。您将查询从onSaveInstanceState() bundle传递到ViewModel,该确定它已经加载了必要的数据,并且不需要重新查询数据库。

注意:最初创建activity时,onSaveInstanceState() bundle不包含任何数据,ViewModel对象为空。创建ViewModel对象时,传递一个空查询,该查询告诉ViewModel对象尚无加载数据。因此,activity以空状态开始。

其他资源

要了解有关保存UI状态的更多信息,请参阅以下资源。

Blogs

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

推荐阅读更多精彩内容

  • 《金文诚〈孟子〉学习笔记607,尽心章13-1-3-1》 【"夭寿不贰,修身以俟之,所以立命也。"】 今天是戊戌年...
    金吾生阅读 215评论 0 0
  • 今天不算太忙,把所有自己的事都全部整理了一下,以后 再做什么事 把自己的事先做完,管好自己分内的事,把自己的的事做...
    AAAAA京心达张水尚阅读 119评论 0 0
  • 我有一颗侧门牙松了,晚上我妈妈就带我去牙科那里拔牙。路上的时候我还没开始拔牙,我就已经害怕了。因为,我觉得拔牙很痛...
    卢思露阅读 283评论 0 0
  • 早睡早起身体好。 今天真是很大强大的训练啊。还得有教练,自己不能对自己下不了这样的狠手。所以就说,自己练的时候很难...
    悟空金月饺子阅读 104评论 0 0