《彻底搞懂 ViewModel:作用、原理与源码分析》
- Android
- 7天前
- 11热度
- 0评论
在Android应用开发中,ViewModel 是Jetpack架构组件库中的核心成员之一,主要用于管理和存储与UI相关的数据。许多开发者在日常工作中虽然频繁使用ViewModel,但对其底层机制、生命周期绑定方式以及配置变更时的存活原理往往一知半解。本文旨在深入剖析ViewModel的设计哲学、核心类结构及其源码实现逻辑,帮助开发者从“会用”进阶到“懂原理”。
ViewModel的核心价值在于它将视图数据从Activity或Fragment的生命周期中解耦出来。传统开发中,屏幕旋转等配置变更会导致Activity重建,进而造成数据丢失或需要繁琐的状态保存与恢复操作。而ViewModel通过ViewModelStore机制,确保在配置变更期间数据得以保留,仅在宿主组件真正销毁时才进行清理。理解这一机制不仅有助于编写更健壮的代码,还能有效避免内存泄漏和重复网络请求等问题。本文将从基本概念入手,逐步拆解ViewModel的创建流程、缓存机制以及关键源码实现,为构建高质量Android应用提供理论支撑。
ViewModel的核心概念与设计初衷
什么是ViewModel?
根据Google官方文档的定义,ViewModel 类是一种用于存储和管理界面相关数据的容器。它的主要职责是将业务逻辑与UI控制器(如Activity和Fragment)分离,从而实现关注点分离(Separation of Concerns)。ViewModel感知生命周期,但其生命周期比创建它的Activity或Fragment更长。具体来说,当宿主组件因配置更改(如屏幕旋转)而被销毁并重新创建时,ViewModel实例不会随之销毁,而是被保留并传递给新的组件实例。
这种设计解决了两个主要痛点:
- 数据持久化:在配置更改期间无需重新获取数据(如网络请求结果),提升了用户体验和应用性能。
- 解耦业务逻辑:将数据处理逻辑从UI控制器中移出,使得代码更易于测试和维护。
需要注意的是,ViewModel并不是无限存活的。当宿主组件(Activity或Fragment)最终被销毁(例如用户按下返回键或系统回收资源)时,ViewModel也会随之清除。因此,它不适合存储需要长期持久化的数据(如用户登录状态),这类数据应存储在Repository层或本地数据库中。
ViewModel的生命周期特征
理解ViewModel的生命周期是正确使用它的前提。ViewModel的生命周期紧密绑定于ViewModelStoreOwner(通常是Activity或Fragment)。其生命周期特征可以概括为以下几点:
- 创建时机:当首次通过ViewModelProvider请求某个ViewModel实例时,如果缓存中不存在,则会创建新实例。
- 存活期间:只要宿主组件处于“活跃”状态或因配置变更而重建,ViewModel就会一直存在。
- 销毁时机:只有当宿主组件真正结束生命周期(即onDestroy()被调用且不是因为配置变更)时,ViewModel才会被清除。
这种机制依赖于Android框架底层的NonConfigurationInstances支持。在Activity重建过程中,系统会将旧的ViewModelStore传递给新的Activity实例,从而实现了数据的无缝衔接。
ViewModel架构中的关键角色与协作关系
要深入理解ViewModel的工作原理,必须厘清几个核心类之间的关系。可以将这一体系比喻为一个“商店模型”,各角色分工明确,协同工作。
核心类定义与职责
ViewModelStoreOwner(仓库所有者)
- 定义:这是一个接口,任何实现该接口的类都可以拥有并管理一个ViewModelStore。
- 典型实现:ComponentActivity和Fragment都实现了此接口。
- 作用:提供获取ViewModelStore实例的方法,确保ViewModel有一个稳定的存储容器。
ViewModelStore(仓库/容器)
- 定义:内部维护了一个HashMap<String, ViewModel>,用于存储ViewModel实例。
- 作用:负责ViewModel的缓存、检索和清除。它是ViewModel能够跨配置变更存活的关键数据结构。
ViewModelProvider.Factory(工厂)
- 定义:用于创建ViewModel实例的策略接口。
- 典型实现:SavedStateViewModelFactory、NewInstanceFactory等。
- 作用:当缓存中不存在所需的ViewModel时,工厂负责根据类类型和额外参数实例化一个新的ViewModel对象。
ViewModelProvider(店员/入口)
- 定义:提供给开发者的主要API入口。
- 作用:协调ViewModelStore和Factory。它首先检查仓库中是否已有对应的ViewModel,如果有则直接返回;如果没有,则调用工厂创建新实例并存入仓库。
协作流程图解
整个获取ViewModel的过程可以简化为以下逻辑流:
- 开发者调用 ViewModelProvider(owner)[MyViewModel::class.java]。
- ViewModelProvider 从 owner(Activity/Fragment)中获取 ViewModelStore。
- ViewModelProvider 生成唯一的Key(通常基于类名)。
- 查询 ViewModelStore 中是否存在该Key对应的实例。
- 若存在:直接返回缓存的实例。
- 若不存在:
- 调用 Factory.create() 创建新实例。
- 将新实例存入 ViewModelStore。
- 返回新实例。
这种“查-创-存”的模式确保了在同一作用域内,同一个ViewModel类只会存在一个单例实例,从而保证了数据的一致性。
ViewModelProvider源码深度解析
从代码层面分析,ViewModelProvider 是开发者与ViewModel体系交互的直接入口。以下是对其核心构造方法和获取逻辑的详细解读。
构造函数分析
ViewModelProvider 提供了多种构造方式,以适应不同的使用场景。最常用的是传入 ViewModelStoreOwner 的方式。
package androidx.lifecycle
/**
* ViewModelProvider:核心入口
*
* 作用:
* - 获取 ViewModel
* - 不存在则创建
* - 并缓存到 ViewModelStore
*/
public actual open class ViewModelProvider
private constructor(
private val impl: ViewModelProviderImpl // ⭐ 真正干活的是它
) {
/**
* 构造方式1(底层)
*
* 传入:
* - store:存储容器
* - factory:创建逻辑
* - extras:额外参数
*/
public constructor(
store: ViewModelStore,
factory: Factory,
defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) : this(ViewModelProviderImpl(store, factory, defaultCreationExtras))
/**
* 构造方式2(最常用)
*
* 直接传 Activity / Fragment
*
* 内部做了:
* - 拿 ViewModelStore
* - 拿默认 Factory
* - 拿默认 Extras
*/
public constructor(owner: ViewModelStoreOwner) : this(
store = owner.viewModelStore,
factory = ViewModelProviders.getDefaultFactory(owner),
defaultCreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
)
/**
* 构造方式3(自定义 Factory)
*/
public constructor(
owner: ViewModelStoreOwner,
factory: Factory,
) : this(
store = owner.viewModelStore,
factory = factory,
defaultCreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
)
// ... 其他方法省略
}在上述代码中,constructor(owner: ViewModelStoreOwner) 是最常见的用法。它内部调用了 ViewModelProviders.getDefaultFactory(owner) 来获取默认的工厂实例。在最新的Jetpack版本中,默认工厂通常是 SavedStateViewModelFactory,它不仅支持普通的ViewModel创建,还支持与 SavedStateHandle 集成,以便在进程死亡后恢复状态。
获取ViewModel的核心逻辑
当我们使用操作符 [] 或调用 get() 方法时,实际上是在委托给内部的 ViewModelProviderImpl 处理。
/**
* 获取 ViewModel(核心入口)
*
* ⭐ 实际调用 impl.getViewModel()
*/
@MainThread
public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
impl.getViewModel(modelClass)
/**
* Java 兼容版本
*/
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T =
get(modelClass.kotlin)这里使用了Kotlin的操作符重载,使得代码更加简洁。真正的逻辑隐藏在 impl 中,这体现了代理模式的思想,将复杂的实现细节封装在内部类中,保持外部API的整洁。
ViewModelProviderImpl:缓存与创建的执行者
ViewModelProviderImpl 是ViewModel机制中真正执行“查找、创建、存储”逻辑的核心类。它的实现保证了线程安全和数据一致性。
线程安全与缓存查找
internal class ViewModelProviderImpl(
private val store: ViewModelStore, // ViewModel 仓库
private val factory: ViewModelProvider.Factory, // ViewModel 工厂
private val defaultExtras: CreationExtras, // 默认附加参数
) {
/**
* 锁对象
*
* 用于保证 getViewModel() 的线程安全
* 避免并发情况下重复创建同一个 ViewModel
*/
private val lock = SynchronizedObject()
/**
* 获取 ViewModel 的核心方法
*/
@Suppress("UNCHECKED_CAST")
internal fun <T : ViewModel> getViewModel(
modelClass: KClass
<T>,
key: String = ViewModelProviders.getDefaultKey(modelClass),
): T {
return synchronized(lock) {
// 1. 先从仓库中取
val viewModel = store[key]
// 2. 如果仓库里已经有,并且类型匹配,直接返回
if (modelClass.isInstance(viewModel)) {
// 如果 Factory 支持“重新查询回调”,则触发
if (factory is ViewModelProvider.OnRequeryFactory) {
factory.onRequery(viewModel!!)
}
return@synchronized viewModel as T
}
// 3. 准备创建 ViewModel 时需要的额外参数
val modelExtras = MutableCreationExtras(defaultExtras)
// 把当前 ViewModel 对应的 key 放进去
modelExtras[ViewModelProvider.VIEW_MODEL_KEY] = key
// 4. 通过工厂创建新的 ViewModel
// 5. 创建后放入仓库
return@synchronized createViewModel(factory, modelClass, modelExtras).also { vm ->
store.put(key, vm)
}
}
}
}这段代码有几个关键点值得注意:
- 同步锁(synchronized):使用 synchronized(lock) 确保在多线程环境下(虽然ViewModel通常应在主线程访问,但底层实现需保证健壮性),同一个Key对应的ViewModel不会被重复创建。
- 类型检查:modelClass.isInstance(viewModel) 确保缓存中的对象类型与请求的类型一致。如果不一致(例如发生了类加载器变化或极端情况),则会视为未命中,重新创建。
- OnRequeryFactory:这是一个高级特性,允许工厂在从缓存返回ViewModel之前执行一些逻辑(如刷新数据),但这在标准使用中较少见。
- CreationExtras:在创建ViewModel时,会传递额外的上下文信息,如VIEW_MODEL_KEY,这对于某些需要知道自身标识的Factory非常有用。
工厂创建机制
当缓存未命中时,createViewModel 方法会被调用。这是一个 expect 函数,意味着它在不同平台(Android JVM, Native等)有不同的 actual 实现。在Android平台上,它最终会调用 Factory.create() 方法。
internal expect fun <VM : ViewModel> createViewModel(
factory: ViewModelProvider.Factory,
modelClass: KClass
<VM>,
extras: CreationExtras,
): VM这种设计使得ViewModel架构具有良好的跨平台扩展性,同时也保持了Android特定实现的灵活性。
ViewModelStore:数据容器的实现细节
ViewModelStore 是ViewModel得以“存活”的物理载体。它是一个简单的键值对存储结构,但其生命周期管理至关重要。
内部结构与基本操作
public open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun put(key: String, viewModel: ViewModel) {
val oldViewModel = map.put(key, viewModel)
// 如果替换了旧实例,需要清除旧实例的资源,防止内存泄漏
oldViewModel?.clear()
}
/** Returns the `ViewModel` mapped to the given `key` or null if none exists. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public operator fun get(key: String): ViewModel? {
return map[key]
}
// clear方法将在后续部分详细讨论
}- put方法:当存入一个新的ViewModel时,如果该Key之前已经关联了一个ViewModel,旧的ViewModel会被移除并调用其 clear() 方法。这一步非常重要,因为它确保了不再需要的ViewModel能够释放其持有的资源(如取消协程、解除监听器等)。
- get方法:简单的Map查找操作,时间复杂度为O(1)。
ViewModelStore的生命周期管理
ViewModelStore 本身并不感知生命周期,它的生命周期完全依赖于持有它的 ViewModelStoreOwner。在 ComponentActivity 中,ViewModelStore 的实例被保存在 NonConfigurationInstances 中。
当Activity因配置变更而重启时:
- 系统调用 onRetainNonConfigurationInstance(),将当前的 ViewModelStore 包裹在 NonConfigurationInstances 对象中返回。
- 新的Activity实例创建后,系统调用 getLastNonConfigurationInstance() 获取之前的对象。
- Activity将提取出的 ViewModelStore 赋值给自己,从而实现了ViewModelStore的复用。
如果Activity是因为用户退出而正常销毁,ComponentActivity 会在 onDestroy() 中调用 viewModelStore.clear(),遍历所有ViewModel并调用它们的 onCleared() 方法,完成最终的资源清理。
总结与实践建议
通过对ViewModel及其相关类的源码分析,我们可以得出以下结论:
- ViewModel是作用域感知的数据容器:它并非独立存在,而是严格绑定于 ViewModelStoreOwner(Activity/Fragment)。
- 缓存机制是核心:ViewModelStore 通过Map缓存实例,确保了在同一作用域内的单例特性,这是避免重复数据加载的关键。
- 工厂模式提供灵活性:ViewModelProvider.Factory 允许开发者自定义ViewModel的创建过程,支持依赖注入(如Hilt/Dagger)和状态保存(SavedState)。
- 生命周期解耦:ViewModel通过在配置变更期间保留 ViewModelStore,实现了与UI生命周期的解耦,但仍随宿主的最终销毁而清理。
实践建议:
- 避免持有View引用:ViewModel不应持有Activity、Fragment或View的引用,否则会导致内存泄漏。如果需要上下文,请使用 AndroidViewModel 获取 Application 上下文。
- 合理使用Scope:在Fragment中获取ViewModel时,注意区分 activityViewModels 和 viewModels。前者共享Activity级别的ViewModel,后者则是Fragment私有的。
- 清理资源:如果ViewModel中启动了协程或注册了监听器,务必在 onCleared() 方法中进行清理。
- 结合LiveData/Flow:ViewModel通常与响应式数据流(LiveData或StateFlow)配合使用,以实现数据驱动UI的最佳实践。
深入理解ViewModel的原理,不仅能帮助开发者规避常见的坑,还能在设计应用架构时做出更合理的决策,构建出更加稳定、高效且易于维护的Android应用。
SavedStateViewModelFactory 的核心机制解析
SavedStateViewModelFactory 是 Android Jetpack 中用于支持状态保存的关键工厂类,它巧妙地将 ViewModel 的生命周期与 SavedStateRegistry 结合起来。通过实现 ViewModelProvider.Factory 接口,该工厂不仅负责实例化 ViewModel,还能自动注入 SavedStateHandle,从而让 ViewModel 具备在进程被杀后恢复数据的能力。在源码实现中,它首先检查传入的 CreationExtras 是否包含必要的注册表所有者和存储所有者信息,确保环境上下文完整。如果检测到目标类继承自 AndroidViewModel,它会优先查找包含 Application 和 SavedStateHandle 的构造函数签名。若未找到匹配的特殊构造函数,则回退到标准的无参或仅含 SavedStateHandle 的构造方式,这种灵活性保证了兼容性与功能性的平衡。此外,它还处理了旧版 API 的兼容逻辑,通过 LegacySavedStateHandleController 确保在不同版本系统上行为的一致性。这种设计模式使得开发者无需手动管理 Bundle 的读写,极大地简化了状态持久化的代码复杂度。
// SavedStateViewModelFactory 创建 ViewModel 的核心逻辑片段
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val key = extras[ViewModelProvider.VIEW_MODEL_KEY]
?: throw IllegalStateException("VIEW_MODEL_KEY must always be provided")
// 检查是否具备保存状态所需的上下文环境
return if (
extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
extras[VIEW_MODEL_STORE_OWNER_KEY] != null
) {
val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
// 根据类型查找匹配的构造函数签名
val constructor: Constructor
<T>? =
if (isAndroidViewModel && application != null) {
findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
} else {
findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
}
// 如果找不到需要 SavedStateHandle 的构造函数,则委托给默认工厂
if (constructor == null) {
return factory.create(modelClass, extras)
}
// 通过反射实例化 ViewModel,并注入 SavedStateHandle
val viewModel =
if (isAndroidViewModel && application != null) {
newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
} else {
newInstance(modelClass, constructor, extras.createSavedStateHandle())
}
viewModel
} else {
// 缺少必要组件时抛出异常,防止状态丢失
throw IllegalStateException(
"SAVED_STATE_REGISTRY_OWNER_KEY and VIEW_MODEL_STORE_OWNER_KEY must be provided"
)
}
}ViewModel 生命周期与配置变更的深度关联
ViewModel 之所以能在屏幕旋转等配置变更中存活,核心在于 ComponentActivity 对 NonConfigurationInstances 机制的精妙运用。当活动因配置改变而销毁时,系统会调用 onRetainNonConfigurationInstance() 方法,此时 ViewModelStore 会被封装进 NonConfigurationInstances 对象中并保留在内存中,而不是随 Activity 一起被垃圾回收。当新的 Activity 实例创建时,attach 方法会接收之前保存的 lastNonConfigurationInstances,并通过 ensureViewModelStore() 方法将其恢复到新的 Activity 实例中。这一过程完全透明于开发者,确保了 ViewModel 中的数据和业务逻辑状态得以无缝延续。值得注意的是,只有在 Activity 真正结束(如用户按下返回键或调用 finish())且 isChangingConfigurations() 返回 false 时,ViewModelStore.clear() 才会被触发。这种机制有效地解耦了 UI 生命周期与数据生命周期,避免了因频繁重建导致的资源浪费和数据丢失风险。
// ComponentActivity 中处理配置变更时保留 ViewModelStore 的逻辑
final void attach(...) {
// 接收上一次配置变更时保存的非配置实例
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
@Override
public final Object onRetainNonConfigurationInstance() {
// 获取自定义的非配置实例数据
Object custom = onRetainCustomNonConfigurationInstance();
// 获取当前的 ViewModelStore
ViewModelStore viewModelStore = mViewModelStore;
// 如果当前没有 ViewModelStore,尝试从上一次实例中恢复
if (viewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
// 如果既没有自定义数据也没有 ViewModelStore,则返回 null
if (viewModelStore == null && custom == null) {
return null;
}
// 将 ViewModelStore 封装并返回,供下一次 Activity 创建时使用
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}ViewModel 的最佳实践与内存泄漏防范
在使用 ViewModel 时,必须严格遵循其设计原则,避免持有任何与 Context 相关的引用,尤其是 Activity 或 View 对象。由于 ViewModel 的生命周期长于 Activity,如果直接持有 Activity 的引用,会导致在 Activity 销毁后无法被垃圾回收,从而引发严重的内存泄漏。对于需要访问上下文资源的场景,应优先使用 AndroidViewModel,它持有的是 Application 级别的 Context,其生命周期与应用进程一致,因此是安全的。此外,ViewModel 应当专注于业务逻辑处理和状态管理,而不应该包含任何 UI 更新逻辑,UI 的观察与更新应交由 LiveData 或 StateFlow 在 Activity/Fragment 中完成。在 Fragment 之间共享数据时,可以通过 requireActivity() 作为 ViewModelProvider 的作用域,实现跨 Fragment 的数据通信,这种方式比传统的接口回调更加简洁且解耦。最后,务必注意不要在 ViewModel 中执行耗时操作而不加以控制,建议结合 CoroutineScope 或 RxJava 进行异步任务管理,并在 onCleared() 方法中正确取消这些任务,以防止后台线程泄露。
// 正确的 ViewModel 使用示例:避免持有 Activity Context
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val _userData = MutableLiveData
<User>()
val userData: LiveData
<User> get() = _userData
// 使用 Application Context 进行数据库操作是安全的
fun loadUser(userId: String) {
viewModelScope.launch {
// 模拟网络请求或数据库查询
val user = Repository.getUser(application.applicationContext, userId)
_userData.postValue(user)
}
}
// 在 ViewModel 销毁时清理资源
override fun onCleared() {
super.onCleared()
// 取消所有协程或关闭流
}
}
// 在 Activity 中观察数据
class UserProfileActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 Activity 作为作用域,确保 ViewModel 伴随 Activity 生命周期
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
viewModel.userData.observe(this) { user ->
// 更新 UI,此处不会导致内存泄漏
updateUI(user)
}
}
}总结:ViewModel 的核心价值与设计哲学
ViewModel 本质上是一个页面作用域的数据与状态容器,其设计哲学在于将数据生命周期从脆弱的 UI 生命周期中剥离出来。它通过 ViewModelStore 进行缓存管理,首次获取时创建实例,后续请求优先复用,从而确保了在横竖屏切换等配置变更场景下的数据连续性。只有当页面真正终结且不再重建时,ViewModelStore.clear() 才会被调用,进而触发 ViewModel 的清理工作。这种机制带来了三大核心价值:首先,它有效保存页面状态,避免了因配置变化导致的数据丢失和重复加载;其次,它承载页面业务逻辑,促使 UI 层与数据处理层解耦,提升了代码的可测试性和可维护性;最后,它提供了标准化的数据共享机制,使得 Activity 与 Fragment 之间、以及多个 Fragment 之间的数据通信变得简单而高效。综上所述,ViewModel 不仅是 Android 架构组件中的基石,更是构建健壮、响应式现代 Android 应用的关键所在,开发者应深刻理解其生命周期特性,以充分发挥其在状态管理方面的优势。