Hi,你好 :)
引言
关于 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() 中进行了初始化。
这也是咱们日常的运用办法,具体咱们这儿就不再做论述。
SavedStateHandle运用办法
咱们知道,ViewModel
能够处理由于装备更改而导致的的状况丢掉,但并不确保反常终止的状况,而官方的 SavedStateHandle
正是用于这种状况的处理办法。
SavedStateHandle
,如名所示,用于保存状况的手柄。再细化点便是,用于保存状况的工具,然后配合 ViewModel
而运用,其内部运用一个 map 保存咱们要存储的状况,并且其自身运用 operator
重载了 set() 与 get() 办法,所以关于咱们来说,能够直接运用 键值对 的办法去操作咱们要保存的状况,这也是官方为什么称 SavedStateHandle
是一个 具有键值映射Map 特性的原因。
在 Fragment1.2 及 Activity1.1.0 之后,
SavedStateHandle
能够作为 ViewModel 的结构函数,然后反射创立带有SavedStateHandle
的 ViewModel 。
具体运用办法如下:
咱们在 MainViewModel
结构函数中新增了一个参数 state:SavedStateHandle ,这个参数在 ViewModel
初始化时,会帮咱们主动进行注入。然后咱们能够利用 SavedStateHandle
以key-value的办法去保存一些 自定义状况 ,然后在进程反常终止,Act重建后,也能获取到之前保存的状况。
至于为什么能完成保存状况呢?
首要是由于 SavedStateHandle
内部默许有一个 SavedStateRegistry.SavedStateProvider 状况保存供给者对象,该对象会在咱们创立ViewModel
时绑定到 SavedStateRegistry 中,然后在咱们 Activity
反常重建时做到状况的 康复 与 绑定 (通过重写 onSavexx()
与 onCreate()
办法监听)。
关于这部分内容,咱们下面的源码解析部分也会再聊到,这儿咱们只需求知道是这么回事即可。
ViewModel源码解析
本章节,咱们将从 ViewModelProvider() 开端,理清 ViewModel
的 创立 与 毁掉 流程,然后了解其背面的 [魔法]。
不过 ViewModel 的源码其实并不是很杂乱,所以别忧虑。
仔细想想,要解析ViewModel的源码,应该从哪里入手呢?
ViewModelProvider(this).get(MainViewModel::class.java)
最简单的办法仍是初始化这儿,所以咱们直接从 ViewModelProvider() 初始化开端->
ViewModelProvider(this)
public constructor(owner: ViewModelStoreOwner)
: this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
相应的,这儿开端,咱们就触及到了三个方面,即 viewModelStore 、 Factory、 Exras 。所以接下来咱们就顺藤摸瓜,分别看看这三处的完成细节。
owner.viewModelStore
ViewModelStoreOwner 望文生义,用于保存 ViewModelStore
对象。
而 ViewModelStore
是担任保护咱们 ViewModel
实例的具体类,内部有一个 map 的合集,用于保存咱们创立的一切 ViewModel
,并对外供给了 clear()
办法,以 便于非装备改变时铲除缓存 。
defaultFactory(owner)
该办法用于初始化 ViewModel
默许的发明工厂 。默许有两个完成,前者是 HasDefaultViewModelProviderFactory ,也是咱们 Fragment
或许 ComponentActivity
都默许完成的接口,而后者是是指全局 NewInstanceFactory 。
两者的不同点在于,后者只能创立 空结构函数 的 ViewModel
,而前者没有这个约束。
示例源码:
HasDefaultViewModelProviderFactory 在 ComponentActivity 中的完成如下:
defaultCreationExtras(owner)
用于辅助 ViewModel
初始化时需求传入的参数,具体源码如下:
如上所示,默许有两个完成,前者是 HasDefaultViewModelProviderFactory ,也便是咱们 ComponentActivity
完成的接口,具体的完成如下:
默许会帮咱们注入 application
以及 intent
等,留意这儿还默许运用了 getIntent().getExtras() 作为 ViewModel
的 默许状况 ,假如咱们 ViewModel
结构函数中有 SavedStateHandle
的话。
更多关于 CreationExtras 能够了解这篇 创立 ViewModel 的新办法,CreationExtras 了解一下?
get(ViewModel::xx)
从缓存中获取现有的 ViewModel
或许 反射创立 新的 ViewModel
。
示例源码如下:
当咱们运用 get() 办法获取具体的 ViewModel
对象时,内部会先利用 当时包名+ViewModel类名 作为 key
,然后从 viewModelStore
中取。假如当时已创立,则直接运用;反之则调用咱们的 ViewModel工厂 create() 办法创立新的 ViewModel
。 创立完成后,并将其保存到 ViewModelStore
中。
create(modelClass,extras)
具体的发明逻辑里,这儿的 factory 正是咱们在 ViewModelProvider
初始化时,默许结构函数 defaultFactory() 办法中生成的SavedStateViewModelFactory ,所以咱们直接去看这个工厂类即可。
具体源码如下:
create(key,modelClass)
兼容旧的版别以及用户操作行为。
相应的,这儿咱们还需求再提一下,LegacySavedStateHandleController.create() 办法:
当咱们调用创立 ViewModel
时,内部会调用具体的 ViewModel
工厂去创立,假如当时 ViewModel
已创立,则直接回来,否则调用其 create() 办法创立新的 ViewModel
。在具体的创立办法中,需求判别当时结构函数是不是带 application
或许 SaveStateHandle
,然后调用合适的 newInstance()
办法,终究再将创立好的 ViewModel
添加到 ViewModelStore
的 缓存 中。
毁掉流程
在初始化 ViewModelProvider
时,还记住咱们需求传递的 ViewModelStoreOwner
吗?
而这个接口正是被咱们的 ComponentActivity 或许 Fragment 各自完成,相应的 ViewModelStore
也是存在于咱们的 ComponentActivity 中,所以咱们直接去看示例代码即可:
以ComponentActivity为例,具体的源码如下:
如上所示:在初始化Activity时,内部会运用 lifecycle
添加一个生命周期观察者,并监听 onDestory() 通知(Act毁掉),假如当时毁掉的原因非装备更改导致,则调用 ViewModeltore.clear() ,即清空咱们的ViewModel缓存列表,然后这也是为什么 ViewModel
不支撑非装备更改的实例保存。
你可能会惊讶,那还怎样借助SavedStateHandle保存状况,viewModel现已被清空了啊?
假如你记住 Activity
传统处理状况的办法,此刻也就能了解为什么了?由于源头都是一个当地,而 SavedStateHandle 仅仅只是一个更简洁的封装而已。不过关于这个问题具体解析,咱们将在下面继续进行讨论,然后了解 SavedStateHandle 的完整流程。
SavedStateHandle流程解析
关于 SavedStateHandle
的运用办法咱们在上面现已叙说过了,其相关的 api 运用源码也不是咱们所重视的重点,由于并不杂乱,而咱们首要要讨论的是其整个流程。
要摸清 SavedStateHandle
的流程,无非就两个方向,即 从何而来 ,又 在哪里进行运用 。
在上面探究 ViewModel
创立流程时,咱们发现,在 get(ViewModel:xx) 办法内部,终究的 create() 办法里,存在两个分支:
- 存在附加参数extras(viewModel2.5.0新增);
- 不存在附加参数extras(兼容前史版别或许用户自定义的行为);
相应的,假如 ViewModel
的结构函数中存在 SavedStateHandle ,则各自的流程如下所示:
- CreationExtras.createSavedStateHandle() ;
- LegacySavedStateHandleController.create(xx).handle ;
前者运用了 CreationExtras 的扩展函数 createSavedStateHandle()
:
而后者运用了 LegacySavedStateHandleController 操控器去创立:
总结:
上述流程中,两者大致是一样的,都需求先调用 consumeRestoredStateForKey(key)
拿到要复原的 Bundle , 再调用 SavedStateHandle.createHandle()
去创立 SavedStateHandle
。
那 SavedStateRegistry 又是什么呢?
咱们的刺进点也就在于此开端。
咱们暂时先不重视怎样复原状况,而是先搞清楚 SavedStateRegistry
是什么,它又是从哪来而传递来的。然后再来看 状况怎样被复原,以及 SavedStateHandle
的创立流程,终究再搞清与 SavedStateRegistry
又是怎样进行相关。
SavedStateRegistry
其是一个用于保存状况的注册表,往往由 SavedStateRegistryOwner 接口所供给完成,然后以便与拥有生命周期的组件相相关。
比方咱们常用的 ComponentActivity
或许 Fragment
默许都完成了该接口。
源码如下所示:
剖析上面的代码不难发现,SavedStateRegistry
自身供给了状况 复原 与 保存 的具体能力,并运用一个 map 保存当时一切的状况供给者,具体的状况供给者由 SavedStateProvider 接口完成。
SavedStateRegistryOwner
相当于是拥有 SavedStateRegistry
的具体类,由于自身承继了 LifecycleOwner
接口,故其也具有 生命感知 能力,如下所示:
interface SavedStateRegistryOwner : LifecycleOwner {
val savedStateRegistry: SavedStateRegistry
}
以 ComponentActivity
为例,咱们会发现,ComponentActivity
默许完成 SavedStateRegistryOwner 接口。即 SavedStateRegistry
的发明以及状况的保存,必定也是 通过咱们Activity转发处理(不然它自己怎样处理呢)。
而在上面探究 ViewModel 初始化时,咱们了解到,ComponentActivity
默许完成了 HasDefaultViewModelProviderFactory
接口,用于创立ViewModel工厂 。相应的,其接口办法 getDefaultViewModelProviderFactory()
默许回来的是 SavedStateViewModelFactory
,即支撑状况保存的ViewModel工厂。而该工厂结构函数中正是需求承受一个 SavedStateRegistry
变量,也正是咱们 ComponentActivity
中默许保存的实例,所以也不难猜测 ViewModel工厂 是怎样与 SavedStateRegistry
怎样相关的。
以 ComponentActivity
的完成为例,源码如下:
ComponentActivity
初始化时,会创立一个 用于保存状况注册表的操控器 SavedStateRegistryController
对象,碰头知意,不难猜出,其是用于操控 SavedStateRegistry
的具体类。并且该操控器对象会在 onCreate() 中调用 performRestore() 复原状况,并在onSaveInstanceState() 中去保存状况,此刻也就解释了为什么 SavedStateRegistry
能做到状况保存。
相应的,咱们仍是要再去看看 SavedStateRegistryController ,以便更好的了解。
SavedStateRegistryController
用于操控 SavedStateRegistry
,对外供给了 初始化 ,状况 复原、保存 等办法,如下所示:
简而言之,其首要用于辅助 SavedStateRegistry 进行状况保存与复原。
小结
咱们再回忆一下上面的过程,在只关怀 SavedStateHandle 怎样被创立这样一个大布景下,咱们大致能够梳理出这样的流程:
由于咱们的 ComponentActivity 或许 Fragment 默许现已完成了 SavedStateRegistryOwner
接口,并且默许是由 SavedStateRegistryController
作为 SavedStateRegistry
的具体操控,因而具体的状况保存与复原都由该操控器去操作。
当咱们的 Activity
由于反常生命周期重建时,此刻会回调 onSaveInstanceState() 去保存状况,此刻 SavedStateRegistryController
就会调用 performSave() 去保存当时状况(即将咱们ViewModel的状况保存到bundle里),然后在 Activity 重建时,在 onCreate() 办法里进行复原(即从bundle里取出咱们保存的状况)。
当咱们创立 ViewModel
时,默许运用的 ViewModel
工厂是支撑保存状况的 SavedStateViewModelFactory
。在初始化该工厂时,需求显式传递 SavedStateRegistryOwner
接口对象到该工厂中,而该工厂的结构函数内,会将 SavedStateRegistry
自行保存起来。
终究,假如要创立的 ViewModel
需求保存状况(即结构函数中存在SavedStateHadnle),则运用保存的 SavedStateRegistry
变量去获取咱们将要复原的状况,然后再调用 SavedStateHandle.createHandle()
去创立具体的 SavedStateHadnle
。
由此结合 ViewModel
创立的流程,咱们能够总结 SavedStateRegistry
的传递流程伪代码如下:
SavedStateHandle怎样创立
在上面,咱们聊完了 SavedStateRegistry
是怎样被创立以及被传递给咱们的 ViewModel工厂 ,而这一末节,咱们将要聊聊 SavedStateHandle
怎样被创立,以及状况是怎样被复原的。
咱们知道,当创立 SavedStateHandle
前,需求先获取已保存的状况,也即 consumeRestoredStateForKey()
办法,所以咱们本章节的刺进点也便是从这儿开端。
而与 consumeRestoredStateForKey()
相关的类有两个, SavedStateHandlesProvider 与 SavedStateRegistry 。
前者是
viewModel
(2.5.0) 新供给的 创立SavedStateHandle 的办法,后者则是用于 适配 2.5.0 之前的办法。
以 SavedStateHandlesProvider 为例,源码如下:
当咱们调用 consumeRestoredStateForKey()
获取具体状况时,内部先会调用 performRestore()
从 SavedStateRegistry 获取咱们保存的状况集,然后将其保存到 provider
中。再从这个总的 状况bundle 中获取咱们当时 viewModel
所对应的状况。
相应的,咱们再去看看 SavedStateHandle.createHandle() 办法,即 SavedStateHandle
终究被怎样创立出来。
源码如下:
上述的逻辑也比较简单,具体如源码中所示,当咱们创立 SavedStateHandle 时,需求先从 SavedStateRegistry 获取咱们的状况Bundle,然后再调用 createHandle()
办法创立具体的 SavedStateHandle。并在其 createHandle()
内将咱们传入的 bundle 转为 Map 办法,然后传入 SavedStateHandle 的结构函数中用于初始化。
总结
在这一章节,咱们首要讨论的是 SavedStateHandle
的创立流程,以 ComponentActivity
为例:
咱们知道 Android 中关于状况的保存与复原,官方建议运用 onSaveInstanceState() 与 onRestoreInstanceState() ,但跟着JetPack组件库的完善,官方在这两个办法的根底上新增了 SavedState
,意图是简化状况保存的本钱。从原理上,其创立了一个 状况保存的的注册表 SavedStateRegistry
,内部缓存着具体的 状况供给者合集(key为string,value为SavedStateProvider)。
当咱们 Activity 由于装备更改或许不可控原因需求重建时,体系此刻会主动调用 onSaveInstanceState() 办法,然后触发调用 savedStateRegistry.performSave()
去保存状况。该办法内部会创立一个新的 Bundle 对象,用于保存一切状况,然后再调用一切缓存的状况供给者(SavedStateProvider)的 saveState()
办法,然后将一切需求需求保存的状况以 key-value 的办法存到 Bundle 中去。终究再将这个全体的 bundle 存入 onSaveInstanceState()
办法参数供给的 bundle 中。
当咱们的 Activity 重建完成后,在 onCreate()
办法中,再运用 SavedStateRegistry
复原咱们自己保存的状况 restoredState。
终究当咱们创立 ViewModel
时,由于咱们的 ViewModel工厂(SavedStateViewModelFactory) 持有了 SavedStateRegistry
,也即持有着咱们要复原的状况(假如有)。在创立具体的 ViewModel
时,假如咱们要创立的 ViewModel
结构函数中存在 SavedStateHandle
参数,则该 ViewModel
支撑保存状况,所以需求先去运用 SavedStateRegistry
获取咱们保存的状况,终究再调用 SavedStateHandle.create() 去创立具体 SaveStateHandle
,然后创立出支撑保存状况 ViewModel
。
结语
在本篇中,咱们从 ViewModel
的布景开端,再到 ViewModel
与 SavedStateHandle
的运用办法,终究又从源码层级剖析了两者的具体流程,然后较完整的解析了 ViewModel
的底层完成与 SavedStateHandle
的全体创立流程。
至于愈加具体的运用办法,这也非本篇要深入探究的细节,具体可参照其他同学的教程即可。
至此,关于 ViewModel
规划思维 以及 状况保存原理 到这儿就完毕了。也相信读过本篇的你也将不会再有所疑惑 :)
参阅
- Android开发者-ViewModel
- 创立 ViewModel 的新办法,CreationExtras 了解一下?
更多
这是 JetPack
组件系列 – ViewModel 篇,假如你觉得这个系列写的还不错,也能够看看其他篇:
- 由浅入深,详解 Lifecycle 的那些事
- 由浅入深,详解 LiveData 的那些事
关于我
我是 Petterp ,一个 Android工程师 ,假如本文对你有所帮助,欢迎点赞支撑,你的支撑是我持续创作的最大鼓励!