一、ViewModel 核心定位

  • 一句话定义:ViewModel 是一个生命周期感知的 UI 数据容器,负责准备和管理 Activity/Fragment 所需的数据,并在配置变更(如屏幕旋转)时保留数据。
  • 解决问题:避免因 Activity 重建导致数据丢失,将数据管理逻辑从 UI 控制器中解耦,提升代码可读性与可测试性。

二、基本使用流程

1. 创建 ViewModel

class MyViewModel : ViewModel() {
    var data: String = ""
}

2. 在 Activity/Fragment 中获取

class MainActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels() // 或 ViewModelProvider(this).get(...)
    override fun onCreate(savedInstanceState: Bundle?) {
        // 直接使用 viewModel.data
    }
}

面试要点

  • 使用 ViewModelProvider(this).get(MyViewModel::class.java) 获取实例,确保同一个 Activity 内多次获取得到同一对象。
  • KTX 扩展 by viewModels() 简化写法,背后原理相同。

三、工作原理与核心源码走读

在这里插入图片描述

1. 涉及的核心类与职责

类/接口 作用
ViewModelStoreOwner 接口,拥有 ViewModelStore,实现类如 ComponentActivityFragment
ViewModelStore 内部用 HashMap<String, ViewModel> 缓存 ViewModel 实例。
ViewModelProvider 工具类,结合 ViewModelStoreFactory 获取或创建 ViewModel。
Factory 负责实例化 ViewModel,默认 NewInstanceFactory 通过反射调用无参构造。

2. ViewModel 获取流程(结合源码)

Factory (NewInstanceFactory) ViewModelStore ViewModelProvider Activity/Fragment Factory (NewInstanceFactory) ViewModelStore ViewModelProvider Activity/Fragment alt [缓存中已有] [缓存不存在] get(MyViewModel.class) get(key) 返回已有 ViewModel create(MyViewModel.class) 新建 MyViewModel 实例 put(key, newViewModel) 存储完成 返回 ViewModel 实例

关键细节

  • 存储 Key 为 DEFAULT_KEY + ":" + 类全限定名,保证同一作用域内唯一。
  • 第一次获取时通过 Factory 反射创建,之后直接从 ViewModelStore 返回,确保同一对象。

直接背诵版:
ViewModel 的获取流程,核心是在 ViewModelProviderget() 方法里完成的,可以概括为“先查缓存,没有就创建,创建完再缓存”。我按源码调用链说一下:

  • 入口
    ViewModelProvider(this).get(MyViewModel.class)

    • 首先会通过ViewModelProvider 的构造函数,依靠传进来的Activity或Fragment 来得到对应的 ViewModelStore
    • 最终会调用到 get(key, modelClass),其中 keyDEFAULT_KEY + ":" + 类全限定名,确保同一个作用域内 ViewModel 的唯一性。
  • 第一步——查缓存
    先通过 mViewModelStore.get(key)ViewModelStore 里取。

    • ViewModelStore 内部就是一个 HashMap<String, ViewModel>
    • 如果能取到,且类型匹配,就直接返回,不会重新创建。
  • 第二步——创建实例
    如果缓存没命中,就通过 Factory 去创建。

    • 默认的 FactoryNewInstanceFactory,底层直接用 modelClass.newInstance() 反射调用无参构造。
    • 如果是 AndroidViewModel,会使用 AndroidViewModelFactory,反射调用带 Application 参数的构造器。
  • 第三步——存入缓存
    创建成功后,立即调用 mViewModelStore.put(key, viewModel) 存起来。

    • put 时会检查是否有旧值,若有则先调旧值的 onCleared(),再放入新实例。
    • 最后返回这个新建的 ViewModel。

补充一点:为什么同一个 Activity 内多次获取是同一个实例?
因为 ViewModelStore 是 Activity 级别的单例容器,Activity 没有真正销毁时,ViewModelStore 一直存在,所以通过同一个 key 拿到的总是同一个 ViewModel。


3. 为什么配置变更(屏幕旋转)时 ViewModel 不销毁?

核心答案ViewModelStore 被保留下来,因为 Activity 的 onRetainNonConfigurationInstance() 将 ViewModelStore 缓存到了系统非配置实例中,重建后通过 getLastNonConfigurationInstance() 取回。

屏幕旋转 配置变更

Activity.onRetainNonConfigurationInstance

将当前 ViewModelStore 存入 NonConfigurationInstances

返回 NonConfigurationInstances 给系统保存

旧 Activity 销毁

新 Activity 创建

getViewModelStore 时调用 getLastNonConfigurationInstance

取到旧 ViewModelStore?

复用旧 ViewModelStore 及其内部的 ViewModel 实例

新建 ViewModelStore

  • 关键方法ComponentActivitygetViewModelStore() 优先从 getLastNonConfigurationInstance() 恢复 mViewModelStore
  • 清理时机:只有 ON_DESTROY非配置变更!isChangingConfigurations())时,才会调用 ViewModelStore.clear(),进而调用 ViewModel.onCleared() 释放资源。

最关键的就一句话:
ViewModel 存在 ViewModelStore 里,而 ViewModelStore 在屏幕旋转时被系统暂存,重建 Activity 后又原样取回来,所以里面的 ViewModel 实例没被销毁。

疑问:Activity 对象被销毁了,为什么它生成的数据NonConfigurationInstances还存在?

在 Android 框架层,系统会在销毁旧 Activity 之前,主动调用 onRetainNonConfigurationInstance() 获取数据,并将其缓存到 ActivityClientRecord(位于 ActivityThread 中),与 Activity 实例本身的销毁无关。新 Activity 重建后,系统再将这份数据注入回去。

每个 Activity 实例拥有自己的 ViewModelStore,每个 Fragment 实例也拥有自己的 ViewModelStore,互不干扰。

Activity、Fragment 实现了ViewModelStoreOwner 这个接口, 唯一抽象方法 getViewModelStore() 是获取当前实例持有的的 ViewModelStore


4. ViewModel 的 onCleared() 方法

  • ViewModel 抽象类中定义了 onCleared(),子类可重写用于清理资源(如取消协程、注销监听)。
  • 被调用时机:当 Activity 真正 finish(非配置变更),ViewModelStore.clear() 遍历所有 ViewModel 并调用其 clear() -> onCleared()

5. AndroidViewModel 与 ViewModel

  • AndroidViewModel 继承 ViewModel,内部持有 Application 引用,方便在 ViewModel 中获取应用级上下文,避免内存泄漏(不持有 Activity)。
  • 实例化时需传入 Application,通过 AndroidViewModelFactory 使用带参构造器反射创建。

面试常用说法:如果需要 Context,优先使用 AndroidViewModel,因为它的生命周期长于 Activity,不会导致泄漏。

四、面试常见问题速答

Q1:ViewModel 和 onSaveInstanceState 的区别?

  • ViewModel:存储复杂对象、存活于内存,配置变更不丢失,但进程被杀死后数据会丢失。
  • onSaveInstanceState:保存少量、可序列化的轻量数据,适合系统杀死进程后的恢复,会被写入磁盘。通常两者结合使用。

Q2:能否在 ViewModel 中持有 Activity 引用?
绝对不能。会导致内存泄漏,因为 ViewModel 的生命周期比 Activity 长。需要 Context 请用 AndroidViewModel 获取 Application

Q3:如何在 Fragment 间共享 ViewModel?
使用 Activity 范围的 ViewModelProvider
ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
这样多个 Fragment 访问的同一 ViewModel 实例。核心是让它们使用同一个 ViewModelStore

requireActivity() 就是拿宿主 Activity 实例,同时也是进入 Activity 作用域 ViewModel 的“钥匙”。

Q4:ViewModel 的 Factory 有什么作用?
当 ViewModel 需要带参构造时(如传入 Repository 或其他依赖),自定义 Factory 来创建实例。默认无参构造器通过反射无法传递参数。

Q5:为什么 ViewModel 内部通常用 LiveData 而不是普通变量?
为了让 UI 能够观察到数据变化并自动更新,符合响应式编程,同时 LiveData 尊重生命周期,防止后台更新已销毁的 UI。

五、记忆要点总结

  1. 四个角色:ViewModelStoreOwner → ViewModelStore → ViewModelProvider.Factory → ViewModel
  2. 获取流程:检查 Store 缓存 → 无则 Factory 创建 → 放入 Store → 返回实例
  3. 旋转不销毁onRetainNonConfigurationInstance() 保留 ViewModelStore,重建时取回
  4. 清理规则:仅当 Activity 真正 finish(非配置变更)才清除
  5. 最佳搭档:LiveData(数据驱动 UI) + Repository(数据层解耦)
  6. 安全使用 Context:优先继承 AndroidViewModel 使用 Application

参考: https://blog.csdn.net/sytregn/article/details/138801493

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐