引言
关于 ViewModel ,Android 开发的小伙伴应该都非常熟悉,无论是新项目还是老项目,基本都会使用到。而 ViewModel 作为 JetPack
核心组件,其本身也更是承担着不可或缺的作用。
因此,了解 ViewModel 的设计思想更是每个应用层开发者必不可缺的基本功。
随着这两年 ViewModel
的逐步迭代,比如 SaveStateHandle 的加入等,ViewModel 也已经不是最初版本的样子。要完全理解其设计体系,往往也要伴随着其他组件的基础,所以并不是特别容易能被开发者吃透。
故本篇将以最新视角开始,与你一起,用力一瞥 ViewModel 的设计原理。
本文对应的组件版本:
- Activity-ktx-1.5.1
- ViewModel-ktx-2.5.1
本篇定位中等,将从背景与使用方式开始,再到源码解读。由浅入深,解析
ViewModel
的方方面面。
导航
学完本篇,你将了解或明白以下内容:
ViewModel
的使用方式;SavedStateHandle
的使用方式;ViewModel
创建与销毁流程;SavedStateHandle
创建流程;
好了,让我们开始吧! 🐊
基础概念
在开始本篇前,我们先解释一些基础概念,以便更加清晰的了解后续的状态保存相关。
何谓配置变更?
配置变更指的是,应用在运行时,内置的配置参数变更从而触发的Activity重新创建。
常见的场景有:旋转屏幕、深色模式切换、屏幕大小变化、更改了默认语言或者时区、更改字体大小或主题颜色等。
何谓异常重建?
异常重建指的是非配置变更情况下导致的 Activity
重新创建。
常见场景大多是因为 内存不足,从而导致后台应用被系统回收 ,当我们切换到前台时,从而触发的重建,这个机制在Android中为 Low Memory Killer
机制,简称 LMK
。
可以在开发者模式,限制后台任务数为1,从而测试该效果。
ViewModel存在之前的世界
在 ViewModel
出现之前,对于 View
逻辑与数据,我们往往都是直接存在 Activity
或者 Fragment
中,优雅一点,会细分到具体的单独类中去承载。当配置变更时,无可避免,会触发界面重绘。相应的,我们的数据在没有额外处理的情况下,往往也会被初始化,然后在界面重启时重新加载。
但如果当前页面需要维护某些状态不被丢失呢,比如 选择、上传状态 等等? 此时问题就变得棘手起来。
稍有经验同学会告诉你,在 onSaveInstanceState 中重写,使用bundle去存储相应的状态啊?➡️
但状态如果少点还可以,多一点就非常头痛,更别提包含继承关系的状态保存。 😶🌫️
所以,不出意外的话,我们 App 的 Activity-manifest 中通常默认都是下列写法:
android:configChanges="keyboard|orientation|uiMode|..."
这也是为啥Android程序普遍不支持屏幕旋转的一部分原因,从源头扼杀因部分配置变更导致的状态丢失问题。🐶保命
VideModel存在之后的世界
随着 ViewModel
组件推出之后,上述因配置变更而导致的状态丢失问题就迎刃而解。
ViewModel
可以做到在配置变更后依然持有状态。所以,在现在的开发中,我们开始将 View数据 与 逻辑 藏于 ViewModel
中,然后对外部暴漏观察者,比如我们常常会搭配 LiveData
一起使用,以此更容易的保持状态同步。
关于 ViewModel
的生命周期,具体如下图所示:
虽然 ViewModel
非常好用,但 ViewModel
也不是万能,其只能避免配置变更时避免状态丢失。比如如果我们的App是因为 内存不足 而被系统kill 掉,此时 ViewModel
也会被清除 🔺 。
不过对于这种情况,仍然有以下三个方法可以依然保存我们的状态:
- 重写
onSaveInstanceState()
与onRestoreInstanceState()
; - 使用
SavedState
,本质上其实还是onSaveInstanceState()
; - 使用
SavedStateHandle
,本质上是依托于SaveState
的实现;
上述的后两种都是随着 JetPack 逐步被推出,可以理解为是对原有的onSavexx的封装简化,从而使其变得更易用。
关于这三种方法,我们会在 SavedStateHandle
流程解析中再进行具体叙述,这里先提出来,留个伏笔。
ViewModel使用方式
作为文章的开始,我们还是要先聊一聊 ViewModel
的使用方式,如下例所示:
当然,你也可以选择引入 activity-ktx ,从而以更简便的写法去写:
implementation 'androidx.activity:activity-ktx:1.5.1' private val mainModel by viewModels<MainViewModel>()
示例比较简单,我们创建了一个 ViewModel
,如上所示,并在 MainActivity
的 onCreate() 中进行了初始化。
这也是我们日常的使用方式,具体我们