重学安卓5-12:再回首 页面搭档 Jetpack ViewModel

作品简介
往期回顾专栏目录更新动态优惠政策版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 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 方法中手动书写保存和恢复语句,

如此后续便容易造成代码修改的不一致,毕竟页面往往有数十个,每个页面需要保存恢复的状态也有数个到数十个不等,修改时总会有疏忽。

img

可能有人会问,旋屏后一样能重新请求和获取数据,为啥要处理状态保存和恢复,
对此我举个例子,方便形成感性认识:页面通常包含来自网络请求的数据,https 请求由于涉及大量加解密运算,耗费 CPU 资源,耗电显著,而 “状态保存和恢复” 能尽可能减少这部分不必要的开销。

场景二:跨页面通信

旧时候在 “首页-详情页” 结构的场景下,两个页面的通信通常是通过 intent(Activity)、接口(Fragment)甚至 EventBus,这么做的弊端主要有:

1.代码修改的一致性问题

bundle 的配置容易存在 “代码修改的不一致”。例如开发者偷懒,不定义 TAG 常量,而是直接在 intent.putExtra TAG 位传入 “字符串的字面量”,那么多个页面间,后续修改该字符串,容易疏忽和不一致,

img

2.Fragment 和 Activity 代码耦合

Fragment 间相互通信,需要定义一系列接口,并以 Activity 作为中转站。也即 Fragment 实例被迫定义在 Activity 中,Fragment 和 Activity 代码相互耦合。特别是如果项目中使用了 Jetpack Navigation,Activity 中无需定义 Fragment 实例,那么这种状况下便无法使用接口通信。

3.组件生命周期安全问题

intent、接口、EventBus 无法兼顾 “组件生命周期安全” 的问题。例如页面 A 由于种种因素,已被 finish 并且成员变量都已为 null,那么页面 B 通过接口、EventBus 等方式通知,极可能在 A 页面触发 NullPointException。
创作时间: