我在上篇博文中用新的 ViewModel 类开发了一个简单的用例来保存配置更改过程当中的篮球分数。ViewModel 被设计用来以与生命周期相关的方式保存和管理 UI 相关的数据。ViewModel 容许数据在例如屏幕旋转这样的配置更改后依然保留。html
如今,你可能会有几个问题是关于 ViewModel 到底能作什么。本文我将解答:前端
onSaveInstanceState()
、本地持久化一块儿使用。简而言之,没有。 还像日常那样去持久化。react
ViewModel 持有 UI 中的临时数据,可是他们不会进行持久化。一旦相关联的 UI 控制器(fragment/activity)被销毁或者进程中止了,ViewModel 和全部被包含的数据都将被垃圾回收机制标记。android
那些被多个应用共用的数据应该像正常那样经过 本地数据库,Shared Preferences,和/或者云存储被持久化。若是你想让用户在应用运行在后台三个小时候后再返回到与以前彻底相同的状态,你也须要将数据持久化。这是由于一旦你的活动进入后台,此时若是你的设备运行在低内存的状况下,你的应用进程是能够被终止的。下面是 activity 类文档中的一个手册表,它描述了在 activity 的哪一个生命周期状态时你的应用是可被终止的:ios
在此提醒,若是一个应用进程因为资源限制而被终止的话,则不是正常终止而且没有额外的生命周期回调。这意味着你不能依赖于 onDestroy
调用。在进程终止的时候你没有机会持久化数据。所以若是你想最大可能的保持数据不丢失,你应该在用户一进入(activity)的时候就进行持久化。也就是说即使你的应用在因为资源限制而被终止或者设备电量用完了的时候数据也将会被保存下来。若是你容许在相似设备忽然关机的状况下丢失数据,你能够在 'onStop()'回调的时候将其保存,这个方法在 activity 一进入后台的时候就会被调用。github
简而言之,不是, 可是他们不无关联,请继续读。web
理解 onSaveInstanceState()
和 Fragment.setRetainInstance(true)
两者之间的不一样有助于理解了解这种差别的微妙之处。数据库
onSaveInstanceState(): 这个回调是为了保存两种状况下的少许 UI 相关的数据:后端
onSaveInstanceState()
是被系统在 activity stopped 但没有 finished 时调用的,而不是在用户显式地关闭 activity 或者在其余情形而致使 finish()
被调用的时候调用。
注意,不少 UI 数据会自动地被保存和恢复:
“该方法的默认实现保存了关于 activity 的视图层次状态的临时信息,例如 EditText 控件中的文本或者 ListView 控件中的滚动条位置。” — Saving and Restoring Instance State Documentation。
这些也是很好的例子说明了 onSaveInstanceState()
方法中存储的数据的类型。onSaveInstanceState()
不是被设计来存储相似 bitmap 这样的大的数据的。onSaveInstanceState()
方法被设计用来存储那些小的与 UI 相关的而且序列化或者反序列化不复杂的数据。若是被序列化的对象是复杂的话,序列化会消耗大量的内存。因为这一过程发生在主线程的配置更改期间,它须要快速处理才不会丢帧和引发视觉上的卡顿。
Fragment.setRetainInstance(true):Handling Configuration Changes documentation 描述了在配置更改期间的一个用来存储数据的进程使用了一个保留的 fragment。这听起来没有 onSaveInstanceState()
涵盖了配置更改和进程关闭两种状况那么有用。建立一个保留 fragment 的好处是这能够保存相似 image 那样的大型数据集或者网络链接那样的复杂对象。
ViewModel 只能在配置更改相关的销毁的状况下保留,而不能在被终止的进程中存留。 这使 ViewModel 成为搭配 setRetainInstance(true)
(实际上,ViewModel 在幕后使用了一个 fragment 并将 setRetainInstance 方法中的参数设置为 true) 一块使用的 fragment 的一种替代品。
ViewModel 和 onSaveInstanceState()
在 UI 数据的存储方法上有很大差异。onSaveInstanceState()
是生命周期的一个回调函数,而 ViewModel 从根本上改变了 UI 数据在你的应用中的管理方式。下面是使用了 ViewModel 后比 onSaveInstanceState()
以外的更多的一些好处:
onSaveInstanceState()
被设计用来存储少许的临时数据,而不是复杂的对象或者媒体数据列表。一个 ViewModel 能够代理复杂数据的加载,一旦加载完成也能够做为临时的存储。onSaveInstanceState()
在配置更改期间和 activity 进入后台时被调用;在这两种状况下,若是你的数据被保存在 ViewModel 中,实际上并不须要从新加载或者处理他们。简而言之,你能够混合使用 ViewModel、 onSaveInstanceState()
、本地持久化。继续读看看如何使用。
重要的是你的 activity 维持着用户指望的状态,即使是屏幕旋转,系统关机或者用户重启。如我刚才所说,不要用复杂对象阻塞 onSaveInstanceState
方法一样也很重要。你也不想在你不须要的时候从新从数据库加载数据。让咱们看一个 activity 的例子,在这个 activity 中你能够搜索你的音乐库:
Activity 未搜索时及搜索后的状态示例。
用户离开一个 activity 有两种经常使用的方式,用户指望的也是两种不一样的结果:
为了实现这两种情形下的行为,用能够将本地持久化、ViewModel 和 onSaveInstanceState()
一块儿使用。每一种都会存储 activity 中使用的不一样数据:
本地持久化是用于存储当打开或关闭 activity 的时全部你不想丢失的数据。
举例: 包含了音频文件和元数据的全部音乐对象的集合。
ViewModel 是用于存储显示相关 UI 控制器的所需的全部数据。
举例: 最近的搜索结果。
onSaveInstanceState 是用于存储在 UI 控制器被系统终止又重建后能够轻松地从新加载 activity 状态时所需的少许数据。在本地存储中持久化复杂对象,在 onSaveInstanceState()
中为这些对象存储惟一的 ID,而不是直接存储复杂对象。 举例: 最近的搜索查询。
在音乐搜索的例子中,不一样的事件应该被这样处理:
用户添加一首音乐的时候 — ViewModel 会迅速代理本地持久化这条数据。若是新添加的音乐须要在 UI 上显示,你还应该更新 ViewModel 中的数据来反应音乐的添加。谨记切勿在主线程中向数据库插入数据。
当用户搜索音乐的时候 — 任何从数据库为 UI 控制器加载的复杂音乐数据应该立刻存入 ViewModel。你也应该将搜索查询自己存入 ViewModel。
当这个 activity 处于后台而且被系统终止的时候 — 一旦 activity 进入后台 onSaveInstanceState()
就会被调用。你应将搜索查询存入 onSaveInstanceState()
的 bundle 里。这些少许数据易于保存。这一样也是使 activity 恢复到当前状态所需的全部数据。
当 activity 被建立的时候 — 可能出现三种不一样的方式:
onSaveInstanceState()
方法中的 bundle 里是没有数据的,ViewModel 也是空的。建立 ViewModel 时,你传入一个空查询,ViewModel 会意识到尚未数据能够加载。这个 activity 以一种全新的状态启动起来。onSaveInstanceState()
的 bundle 中保存了查询。Activity 会将这个查询传入 ViewModel。ViewModel发现缓存中没有搜索结果,就会使用给定的搜索查询代理加载搜索结果。onSaveInstanceState()
的 bundle 参数中而且 ViewModel 也会将搜索结果缓存起来。你经过 onSaveInstanceState()
的 bundle 将查询传入 ViewModel,这将决定它已加载了必须的数据从而不须要从新查询数据库。这是一个良好的保存和恢复 activity 状态的方法。基于你的 activity 的实现,你可能根本不须要 onSaveInstanceState()
。例如,有些 activity 在被用户关闭后不会以一个全新的状态打开。通常地,当我在 Android 手机上关闭而后从新打开 Chrome 时,返回到了关闭 Chrome 以前正在浏览的页面。若是你的 activity 行为如此,你能够不使用 onSaveInstanceState()
而在本地持久化全部数据。一样以音乐搜索为例,那意味着在例如 Shared Preferences 中持久化最近的查询。
此外,当你经过 intent 打开一个 activity,配置更改和系统恢复这个 activity 时 bundle 参数都会被传进来。若是搜索查询是经过 intent 的 extras 传进来,那么你就可使用 extras 中的 bundle 代替 onSaveInstanceState()
中的 bundle。
不过,在这两种场景中,你仍须要一个 ViewModel 来避免因配置更改而从新从数据库中加载数据致使的资源浪费。
简而言之,对,ViewModel 结合其余几个类能够代替 Loader 使用。
Loader 是 UI 控制器用来加载数据的。此外,Loader 能够在配置更改期间保留,好比说在加载的过程当中你旋转了手机屏幕。这听起来很耳熟吧!
Loader ,特别是 CursorLoader,的常见用法是观察数据库的内容并保持数据与 UI 同步。使用 CursorLoader 后,若是数据库其中的一个值发生改变,Loader 就会自动触发数据从新加载而且更新 UI。
ViewModel 与其余架构组件 LiveData 和 Room 一块儿使用能够替代 Loader。ViewModel 保证配置更改后数据不丢失。LiveData 保证 UI 与数据同步更新。Room 确保你的数据库更新时,LiveData 被通知到。
因为 Loader 在 UI 控制器中做为回调被实现,所以 ViewModel 的一个额外优势是将 UI 控制器与数据加载分离开来。这能够减小类之间的强引用。
一些使用 ViewModels 、LiveData 为加载数据的方法:
“仓库模块负责处理数据操做。他们为应用的其余部分提供了一套干净的 API。当数据更新时他们知道从哪里获取数据以及调用哪一个 API。你能够把他们当作是不一样数据源(持久模型、web service、缓存等)之间的协调员。” — Guide to App Architecture
在本文中,我回答了几个关于 ViewModel 类是什么和不是什么的问题。关键点是:
onSaveInstanceState()
的替代品,由于他们在与配置更改相关的销毁时保存数据,而不能在系统杀死应用进程时保存。onSaveInstanceState()
并不适用于那些须要长时间序列化/反序列化的数据。onSaveInstanceState()
和 ViewModel。复杂数据经过本地持久化保存而后用 onSaveInstanceState()
来保存那些复杂数据的惟一 ID。ViewModel 在数据加载后将他们保存在内存中。onSaveInstanceState()
并没那么容易实现。想要更多 ViewModel 相关的干货?请看:
架构组件是基于你反馈来建立的。若是你有关于 ViewModel 或者任何架构组件的问题,请查看咱们的反馈页面。关于本系列的任何问题,敬请留言。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。