源码解析Android Jetpack组件之ViewModel的使用
作者:孙先森Blog 发布时间:2023-04-23 01:10:53
前言
在之前 LiveData 源码浅析的博客中提到了 ViewModel 组件,当时对 ViewModel 的解释是 “生命周期比Activity” 更长的对象。本文就来了解下其实现原理。
依赖版本
// 注意这里的 appcompat、activity-ktx、fragment-ktx
// 高版本的自动引入了 viewmodel-savedstate 实战中很少用到的功能
// 篇幅原因 就不再本文中分析 viewmodel-savedstate 扩展组件了
implementation 'androidx.appcompat:appcompat:1.0.0'
def fragment_version = "1.1.0"
def activity_version = "1.0.0"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
基础使用
定义
class MainViewModel: ViewModel(){ ... }
// or
class MainViewModel(application: Application): AndroidViewModel(application){
val data: String = ""
fun requestData(){
data = "xxx"
}
}
在 MainVieModel 中可以定义 UI 界面中需要的数据(对象、LiveData、Flow 等等)和方法,在 Activity 真正销毁前 ViewModel 中的数据不会丢失。
Activity 中获取
val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or
// 引入 activity-ktx 库可以这样初始化 ViewModel
val vm by viewModels<MainViewModel>()
// 通过 vm 可以调用其中的方法、获取其中的数据
vm.requestData()
Log.e(TAG, vm.data)
Fragment 中获取
val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or
// 获取和 Activity 共享的 ViewModel 也就是同一个 ViewModel 对象
val vm = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
引入 fragment-ktx 可以这样初始化
val vm = viewModels<MainViewModel>()
// or 效果同上
val vm = activityViewModels<MainViewModel>()
前置知识
ViewModel 的使用非常简单,也很容易理解,就是一个生命周期长于 Activity 的对象,区别在于不会造成内存泄漏。ViewModel 不是魔法,站在开发者的角度在 ViewModel 没有问世之前横竖屏切换需要保存状态数据的需求通常都是通过 onSaveInstanceState、onRestoreInstanceState 来实现。
onSaveInstanceState、onRestoreInstanceState
关于这两个方法这里就简单概述一下:onSaveInstanceState 用于在 Activity 横竖屏切换(意外销毁)前保存数据,而 onRestoreInstanceState 是用于 Activity 横竖屏切换(重建)后获取保存的数据;
onSaveInstanceState 调用流程
由于是在 Activity 销毁前触发,那么直接来 ActivityThread 中找到 performPauseActivity 方法:
ActivityThread.java
private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, PendingTransactionActions pendingActions) {
// ...
if (shouldSaveState) {
callActivityOnSaveInstanceState(r);
}
// ...
}
private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
// ...
// 这里通过 ActivityClientRecord 获取到 activity
// state 是 Bundle 对象,后面要保存的数据就放在 state 中
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
// ...
}
这里有 ActivityThread 调用到了 Instrumentation 中,继续看源码:
Instrumentation.java
public void callActivityOnSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
activity.performSaveInstanceState(outState);
}
根据传入的 activity 调用其 performSaveInstanceState 方法:
Activity.java
final void performSaveInstanceState(@NonNull Bundle outState) {
onSaveInstanceState(outState);
}
总结一下,onSaveInstanceState 中我们将数据存储在 Bundle 对象中,而这个 Bundle 对象是存储在 ActivityClientRecord 中。
onRestoreInstanceState 调用流程
看完了 onSaveInstanceState 的调用流程,那么 onRestoreInstanceState 的流程就来简单说说,由于在 onStart 后发生回调,所以直接去看 ActivityThread 中的源码:
ActivityThread.java
public void handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
// ...
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
// ...
}
可以看出这里从 ActivityClientRecord 中取出了 activity 和 state 进行传毒,后面就和 onSaveInstanceState 调用流程一样了,源码比较简单就不贴了。
onRetainCustomNonConfigurationInstance、getLastCustomNonConfigurationInstance
除了 onSaveInstanceState 和 onRestoreInstanceState,在 Activity 中还有一组方法可以实现类似的功能,就是 onRetainCustomNonConfigurationInstance 和 getLastCustomNonConfigurationInstance,前者即保存数据,后者即获取保存的数据;
简单使用
override fun onRetainCustomNonConfigurationInstance(): Any? {
val data = SaveStateData()
return data
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 获取保存的数据
val data = getLastCustomNonConfigurationInstance() as SaveStateData
}
和 onSaveInstanceState 使用的区别在于 onSaveInstanceState 只能在其参数中的 Bundle 对象中写入数据,而 onRetainCustomNonConfigurationInstance 返回的类型是 Any(Java Object)不限制数据类型。老样子看一下这组方法的源码调用流程。
onRetainCustomNonConfigurationInstance
onRetainCustomNonConfigurationInstance 是在 ComponentActivity 中定义的,默认实现返回 null,其在 onRetainNonConfigurationInstance 方法中被调用:
ComponentActivity.java
public Object onRetainCustomNonConfigurationInstance() {
// ComponentActivity 中默认返回 null
return null;
}
public final Object onRetainNonConfigurationInstance() {
// 保存在了 custom 变量中
Object custom = onRetainCustomNonConfigurationInstance();
// 这里已经出现 ViewModel 相关的源码了,这里先按下不表
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
// 新建 NonConfigurationInstances 对象
NonConfigurationInstances nci = new NonConfigurationInstances();
// custom 赋值给了 NonConfigurationInstances 对象
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
从 ComponentActivity 的这部分源码中可以看出保存的数据最终放在了 NonConfigurationInstances 对象的 custom 属性中;接着找 onRetainNonConfigurationInstance 的定义,在 Activity 中:
Activity.java
public Object onRetainNonConfigurationInstance() {
// 默认返回 null
return null;
}
NonConfigurationInstances retainNonConfigurationInstances() {
// ComponentActivity 中返回的 NonConfigurationInstances 对象
Object activity = onRetainNonConfigurationInstance();
// ...
// 注意 这里有新建另一个 NonConfigurationInstances 对象
NonConfigurationInstances nci = new NonConfigurationInstances();
// ComponentActivity 中返回的 NonConfigurationInstances 对象
// 存储到了新的 NonConfigurationInstances 中的 activity 属性中
nci.activity = activity;
// ...
return nci;
}
在 Activity 类中相当于做了一层套娃,又新建了一个 NonConfigurationInstances 对象,将 ComponentActivity 中返回的 NonConfigurationInstances 对象存了进去;
其实源码看到这里就可以了,不过本着刨根问底的原则,我们接着再看一下 NonConfigurationInstances 到底存在了哪里?在 ActivityThread.java 中找到了调用 retainNonConfigurationInstances 的地方:
ActivityThread.java
void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
// ...
// 这个 r 是参数中的 ActivityClientRecord
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}
和 onSaveInstanceState 一样存储在了 ActivityClientRecord 中,只不过换了一个属性罢了。
getLastCustomNonConfigurationInstance
看完了存储的流程,简单来看看取数据的流程。既然存的时候套娃了一下 NonConfigurationInstances,那取数据的时候肯定也需要套娃:
ComponentActivity.java
public Object getLastCustomNonConfigurationInstance() {
// 通过 getLastNonConfigurationInstance 获取 NonConfigurationInstances
NonConfigurationInstances nc = (NonConfigurationInstances)
getLastNonConfigurationInstance();
// 返回 custom
return nc != null ? nc.custom : null;
}
那么在 Activity 中肯定还需要取一次 ActivityClientRecord 中的 NonConfigurationInstances:
Activity.java
NonConfigurationInstances mLastNonConfigurationInstances;
public Object getLastNonConfigurationInstance() {
// 返回其 activity 字段
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
// mLastNonConfigurationInstances 赋值在 attach 方法中
final void attach(Context context, /*参数太多 省略了*/ NonConfigurationInstances lastNonConfigurationInstances) {
// ...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
// ...
}
可以看出在 Activity attach 方法中就已经拿到了套娃后的 NonConfigurationInstances 对象,我们都知道 Activity attach 方法是在 ActivityThread 的 performLaunchActivity 中调用,看一下源码:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
// 参数太多 省略了
// 可以看到是从 ActivityClientRecord 中取出传入的
activity.attach(appContext, r.lastNonConfigurationInstancesn);
// ...
}
小节总结
两种方式都是将数据保存到了 ActivityClientRecord 中,不同的是前者限制了 Bundle 类型,后者不限制类型(ViewModel 采用的就是后者这组方法实现),不过后者已经在源码中被标记了删除,并不影响使用,标记删除是为了让开发者们利用 ViewModel 来接管这种需求。下面我们就正式进入 ViewModel 源码。
源码分析
前置知识有点长,不过也几乎把 ViewModel 的原理说透了,ViewModel 的保存、恢复是利用了系统提供的方法,不过还有些细节还需要在源码中探索,比如:如何实现 Activity/Fragment 共享 ViewModel?接下来就来深入 ViewModel 源码。
创建
先来以 Activity 中创建 ViewModel 的这段代码入手:
val vm by viewModels<MainViewModel>()
查看 viewModels 源码:
// 这是一个 ComponentActivity 的扩展方法
@MainThread // 在主线程中使用
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
// 从命名也可以看出是一个工厂模式,默认是 null
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
// 默认 factoryProducer 为 null
// 返回的是 AndroidViewModelFactory
val factoryPromise = factoryProducer ?: {
val application = application ?: throw IllegalArgumentException(
"ViewModel can be accessed only when Activity is attached"
)
AndroidViewModelFactory.getInstance(application)
}
// 返回了一个 ViewModelLazy 对象,将 viewModelStore、factoryProducer 传入
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
到这里先暂停看一下 AndroidViewModelFactory 是如何初始化的,以及 viewModelStore 是什么东东:
ViewModelProvider.kt
private var sInstance: AndroidViewModelFactory? = null
@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
if (sInstance == null) {
sInstance = AndroidViewModelFactory(application)
}
return sInstance!!
}
是一个单例模式,直接对 AndroidViewModelFactory 进行实例化,再来看看 mViewModelStore
ComponentActivity.java
// 都是定义在 ComponentActivity 中的变量,默认 null
private ViewModelStore mViewModelStore;
public ViewModelStore getViewModelStore() {
// ...
if (mViewModelStore == null) { // 第一次启动 activity 为 null
// 获取保存的数据
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
// 优先从保存的数据中获取
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
// 默认返回 ViewModelStore
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
ViewModelStore 内部仅仅是管理一个 Map<String, ViewModel>,用于缓存、清理创建的 ViewModel。
回过头接着看扩展方法 viewModels 返回的 ViewModelLazy:
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass<VM>, // ViewModel 的 class
private val storeProducer: () -> ViewModelStore, // 默认是 ViewModelStore
private val factoryProducer: () -> ViewModelProvider.Factory, // 这里就是 mDefaultFactory
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty } //
) : Lazy<VM> { // 注意这里返回的 Lazy,延迟初始化
private var cached: VM? = null
override val value: VM
get() { // 由于返回的是 Lazy,也就是当使用 ViewModel 时才会调用 get
val viewModel = cached
return if (viewModel == null) { // 第一次调用是 null,进入 if
val factory = factoryProducer() // mDefaultFactory
val store = storeProducer() // ViewModelStore
ViewModelProvider( // 生成 ViewModelProvider 对象
store,
factory,
extrasProducer()
).get(viewModelClass.java).also { // 调用其 get 方法获取 ViewModel
cached = it // 保存到 cached 变量
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
这里又出现了一个陌生的对象 CreationExtras,其内部也是一个 map,可以理解为一个键值对存储对象,只不过他的 Key 是一个特殊类型。
接着查看 ViewModelProvider 的 get 方法是如何创建 ViewModel 的:
// 存储ViewModel的key的前缀
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
// 调用重载方法,拼接 key 传入
// 当前key即为:androidx.lifecycle.ViewModelProvider.DefaultKey$com.xxx.MainViewModel
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key] // 优先从 ViewModelStroe 中获取缓存
if (modelClass.isInstance(viewModel)) { // 如果类型相同 直接返回
// 这里我们的 factory 是 AndroidViewModelFactory 所以不会走这行代码
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
}
// ...
// 这里的 defaultCreationExtras 是上一步骤中的 CreationExtras,默认值为 CreationExtras.Empty
// MutableCreationExtras 包装一层就是将 defaultCreationExtras 中所有的键值对都copy一份
val extras = MutableCreationExtras(defaultCreationExtras)
// 将当前 ViewModel 的 key 存储进去
extras[VIEW_MODEL_KEY] = key
return try {
// 优先调用双参数方法
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
// 调用双参数方法发生异常再调用单参数方法
factory.create(modelClass)
}.also {
// 获取到 ViewModel 后存储到 viewModelStore 中
// 再提一嘴 viewModelStore 是在 ComponentActivity 中定义
store.put(key, it)
}
}
终于到了创建 ViewModel 的部分了,直接去看 AndroidViewModelFactory 的 create 方法:
ViewModelProvider.kt
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
// application 不为 null 调用单参数方法
// 在新建 AndroidViewModelFactory 已经传入了 application,一般情况不为 null
return if (application != null) {
create(modelClass)
} else {
// application 如果为 null,则会从传入的 extras 中尝试获取
val application = extras[APPLICATION_KEY]
if (application != null) {
// 这个 create 也是双参数,但不是递归,第二个参数是 application,源码贴在下面
create(modelClass, application)
} else {
// 如果 application 仍然为 null,且 ViewModel 类型为 AndroidViewModel 则抛异常
if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
throw IllegalArgumentException(...)
}
// 类型不是 AndroidViewModel 则根据 class 创建
// 注意这里调用的 super.create 是父类方法
// 父类方法直接根据 modelClass.newInstance() 创建,就一行就不贴源码了
super.create(modelClass)
}
}
}
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (application == null) { // application 为 null 直接抛异常
throw UnsupportedOperationException(...)
} else {
// 调用下面的双参数方法
create(modelClass, application)
}
}
private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
// 如果是 AndroidViewModel 类型则获取带 application 的构造参数创建
return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
modelClass.getConstructor(Application::class.java).newInstance(app)
} else {
// 直接调用父类 create 方法通过 modelClass.newInstance() 创建
super.create(modelClass)
}
}
至此 Activity 中的 ViewModel 创建过程源码就全部分析完了,总结一下:Activity 中的 ViewModel 创建都是通过单例工厂 AndroidViewModelFactory 的 create 方法中反射创建,在调用 create 创建前会生成字符串 key,创建完成后会将 key 和 vm 对象存储到 ViewModelStore 中,后续获取将优先从 ViewModelStore 缓存中获取。
ViewModelStore 是定义在 ComponentActivity 中的,ViewModel 生命周期 “长于” Activity 的原理跟这个 ViewModelStore 脱不了干系。
恢复
前面小节提过,ViewModel 的恢复利用的是 onRetainNonConfigurationInstance 方法,ViewModelStore 又是定义在 ComponentActivity 中,那么直接去看 ComponentActivity 这部分的源码:
ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
// 留给开发者使用的字段
Object custom = onRetainCustomNonConfigurationInstance();
// 获取当前 Activity 的 mViewModelStore
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// 如果为 null 则尝试获取上一次保存的数据
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 获取上一次存储的 viewModelStore
viewModelStore = nc.viewModelStore;
}
}
// ...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom; // 开发者用的字段
nci.viewModelStore = viewModelStore; // 保存 viewModelStore 的字段
return nci;
}
再来看一看 ViewModelStore 的获取方法:
public ViewModelStore getViewModelStore() {
// ...
if (mViewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
// 优先从保存的数据中获取 viewModelStore
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
// 获取不到才会新建
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
Activity 获取 mViewModelStore 时优先从 getLastNonConfigurationInstance 获取到 NonConfigurationInstances 对象,再从其中获取 viewModelStore,这样在当前 Activity 作用域中创建过的 ViewModel 都存储在 ViewModelStore 中,当需要再次使用时走 ViewModel 创建流程会直接从 ViewModelStore 中返回。
最后
再了解了 onRetainNonConfigurationInstance 这组方法之后再来探究 ViewModel 的恢复原理就很简单了,onRetainNonConfigurationInstance 也被标记为了删除,google 也希望开发者尽可能的使用 ViewModel 来保存数据(临时数据)。
onRetainNonConfigurationInstance 虽然被标记为删除,但仍然可以正常使用,相比于 onSaveInstanceState 没有了数据类型限制,但并不意味着我们可以随意存储,比较大的数据还是应该考虑持久化存储。
来源:https://juejin.cn/post/7210688688922361893


猜你喜欢
- 效果图白话分析:多线程:肯定是多个线程咯断点:线程停止下载的位置续传:线程从停止下载的位置上继续下载,直到完成任务为止。核心分析:断点:当前
- Android 8.0推出了PictureInPicture(画中画功能),目前只有在8.0以上的系统上支持。对比IOS,IOS的Pictu
- 先来看看效果图跳动的小球做这个动画,需掌握: 1、属性动画  
- Bean Validation 中内置的 constraint @Null 被注释的元素必须为 null @NotNull 被注释的元素必须
- 一、背景今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib * 哪个效率更高?二、基本概念首先,我们知道Sprin
- 本文实例为大家分享了Android向node.js服务器发送数据并接收请求的具体代码,供大家参考,具体内容如下首先时node.js服务器端代
- 我们将会从以下的几点理解java线程的一些概念:线程的基本概念和优劣之处创建一个线程的两种方式线程的属性线程的状态synchronized可
- 本文实例讲述了java实现MD5加密的方法。分享给大家供大家参考,具体如下:private String getMD5Str(String
- 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式。它要求从产品中抽离所有地域语言,国家/地
- 本文实例为大家分享了Android 自定义弹窗提示的具体代码,供大家参考,具体内容如下Java文件:private void showSet
- 这篇文章主要介绍了SpringMVC的执行流程及组件详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 目录前言实现思路实测前言需求 导出Excel:本身以为是一个简单得导出,但是每行得记录文件中有一列为图片url,需要下载所有记录行对应得图片
- 本文实例讲述了Android编程使用AlarmManager设置闹钟的方法。分享给大家供大家参考,具体如下:package com.Aina
- IDEA设置Tab选项卡本人喜欢把tab选项卡全部放出来(tab选项卡默认是10个,超过后会把最先打开的挤出去,像队列一样先进先出),比如这
- 前言在前后端分离的应用中,前端往往需要向后端发送命令请求,并将请求中的数据以Json格式传递。后端需要将Json格式的数据反序列化成Java
- 1、反射的概念1、概念反射,指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法。这种
- 一、@EnableTransactionManagement工作原理开启Spring事务本质上就是增加了一个Advisor,但我们使用 @E
- 由于公司项目的需求,需要绘制一条竖直的间断线作为分割线。这个可坑了爹了,以前只搞过水平的间断线,只要通过shape也可以简单的画出来,但是千
- 二、简介多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力,但频繁的创建线程的开
- 1.定义字符串字符串常见的构造方式如下:String s1 = "with";String s2 = new Strin