往期回顾,专栏目录,更新动态,优惠政策,版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 3310 余次修订,数十位群友告诉我,受专栏启发 亦开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创开源库 和 提供内推机会,订阅后可随时进群交流。
·
重要提示:
阅读本文最佳时机是,您已吃过 阅读 “源码” 或 “源码分析文” 时 找不到头绪的苦。
您还没吃过苦,那您先不要着急阅读本文。您得吃过苦,才会有体会。
在您吃够这方面苦后,您才有机会发现,本文正是专用于解决 “如何找到正确打开方式” 的困扰。
我们绝不通篇贴源码,而是基于广泛实践和反思,在累积过大量样本 乃至足以排除掉所有干扰信息后,点到为止揭露 ViewModel 框架最核心本质,方便您理解其真实的存在意义,乃至笃信使用项目中。
关于 ViewModel 在项目中实践,请另行查阅《GitHub : Jetpack-MVVM-Best-Practice》源码以及《架构模式实践》篇 完整解析。
前言
为了更好的阅读体验,一直想着翻新发行于 2019 年的 Jetpack ViewModel 篇,
由于补丁过多难以下手,加之沉淀多年呼之欲出,于是考虑重新写一篇,
借此也邀您一起来回顾一下,关于 ViewModel 的常用技术点,多年来宠幸了哪些、错过了哪些、关注了哪些、又疏忽了哪些。
注:Jetpack ViewModel 下文皆以 “ViewModel” 指代。
文章目录一览
- 前言
- ViewModel 目标只有三个
- ViewModel 问世前的混沌世界
- 场景一:状态保存和恢复
- 场景二:跨页面通信
- 1.代码修改的一致性问题
- 2.Fragment 和 Activity 代码耦合
- 3.组件生命周期安全问题
- ViewModel 为何能解决上述问题
- 场景一:分治 + 解决一致性问题
- 场景二:设置作用域
- 引入 ViewModel 后的世界
- 场景一:状态托管
- 场景二:跨页面通信
- 额外分享的 6 个细节
- 1.不同作用域 == 不同实例
- 2.Activity 间通信可优先考虑 App 级作用域
- 3.旋屏重建时 ViewModelStore 不会被销毁
- 4.不适用 ViewModel 的场景和应对方案
- 5.避免持有页面导致内存泄漏
- 6.通过 “继承” 将职责细分和屏蔽
- 综上
ViewModel 目标只有三个
ViewModel 最早是于 2017 年 Google I/O 大会上被提出,同时间问世还有 Lifecycle 和 LiveData。
谈论 ViewModel 时常常避不开 LiveData。LiveData 就像 ViewModel 左臂右膀,支撑 ViewModel 作为中间层,对状态管理尽 “承上启下” 职责。
故尽管本文目标是介绍 ViewModel 存在缘由、设计依据、职责边界,本着介绍 “相互间关系” 需要,还是会顺带展示 LiveData 身影。
ViewModel 目标只有三个:
1.让状态管理独立于页面,从而实现 “状态管理分治”、“状态管理一致性” 和 “状态共享”,
2.为状态设置作用域,使状态共享做到作用域可控。
3.实现单向依赖,避免内存泄漏。
上述三点,如暂无体会,那接下来,请跟随我脚步铺垫 ViewModel 来龙去脉。
ViewModel 问世前的混沌世界
场景一:状态保存和恢复
旧时候要想保存和恢复页面的成员变量,通常需要手动通过 saveInstanceState 机制完成,
具体而言,每新增一个待保存的变量,都需要我们手动书写 变量本身、常量 TAG、且分别于 onSaveInstanceState 和 onRestoreInstanceState 方法中手动书写保存和恢复语句,
如此后续便容易造成代码修改的不一致,毕竟页面往往有数十个,每个页面需要保存恢复的状态也有数个到数十个不等,修改时总会有疏忽。

可能有人会问,旋屏后一样能重新请求和获取数据,为啥要处理状态保存和恢复,
对此我举个例子,方便形成感性认识:页面通常包含来自网络请求的数据,https 请求由于涉及大量加解密运算,耗费 CPU 资源,耗电显著,而 “状态保存和恢复” 能尽可能减少这部分不必要的开销。
场景二:跨页面通信
旧时候在 “首页-详情页” 结构的场景下,两个页面的通信通常是通过 intent(Activity)、接口(Fragment)甚至 EventBus,这么做的弊端主要有:
1.代码修改的一致性问题
bundle 的配置容易存在 “代码修改的不一致”。例如开发者偷懒,不定义 TAG 常量,而是直接在 intent.putExtra TAG 位传入 “字符串的字面量”,那么多个页面间,后续修改该字符串,容易疏忽和不一致,

2.Fragment 和 Activity 代码耦合
Fragment 间相互通信,需要定义一系列接口,并以 Activity 作为中转站。也即 Fragment 实例被迫定义在 Activity 中,Fragment 和 Activity 代码相互耦合。特别是如果项目中使用了 Jetpack Navigation,Activity 中无需定义 Fragment 实例,那么这种状况下便无法使用接口通信。
3.组件生命周期安全问题
intent、接口、EventBus 无法兼顾 “组件生命周期安全” 的问题。例如页面 A 由于种种因素,已被 finish 并且成员变量都已为 null,那么页面 B 通过接口、EventBus 等方式通知,极可能在 A 页面触发 NullPointException。