本文译自「Inside Jetpack ViewModel: Internal Mechanisms and Multiplatform Design」,原文链接https://proandroiddev.com/inside-jetpack-viewmodel-internal-mechanisms-and-multiplatform-design-2625671eaef8 ,由Jaewoong Eum发布于2025年12月7日。
Jetpack 的 ViewModel 已成为现代 Android 开发中不可或缺的组件,它为 UI 相关数据提供了一个生命周期感知容器,即使配置发生更改,数据也能保持不变。虽然其 API 表面上看起来很简单,但其内部机制却展现了围绕生命周期管理、跨平台抽象、资源清理和线程安全缓存等方面的设计决策。了解 ViewModel 的底层工作原理有助于你做出更优的架构决策,并避免一些不易察觉的错误。
本文将深入探讨 Jetpack ViewModel 的内部工作原理,包括 ViewModelStore 如何在配置更改后保留实例、ViewModelProvider 如何协调创建和缓存、工厂模式如何实现灵活的实例化、CreationExtras 如何实现无状态工厂、如何通过 Closeable 模式管理资源清理,以及 viewModelScope 如何将协程与 ViewModel 生命周期集成。
根本问题:配置更改后的保留
配置更改是 Android 开发面临的一项根本性挑战。当用户旋转设备、更改语言设置或触发任何配置更改时,系统会销毁并重新创建 Activity。Activity 中存储的所有数据都会丢失:
1
2
3
4
5
6
7
8
9
class MyActivity : ComponentActivity () {
private var userData : User ? = null // Lost on rotation!
override fun onCreate ( savedInstanceState : Bundle ?) {
super . onCreate ( savedInstanceState )
// Must reload data after every rotation
loadUserData ()
}
}
一种简单的方法是使用 onSaveInstanceState():
1
2
3
4
5
6
7
8
9
override fun onSaveInstanceState ( outState : Bundle ) {
super . onSaveInstanceState ( outState )
outState . putParcelable ( "user" , userData )
}
override fun onCreate ( savedInstanceState : Bundle ?) {
super . onCreate ( savedInstanceState )
userData = savedInstanceState ?. getParcelable ( "user" )
}
这种方法适用于小型、可序列化的数据。但是,对于大型数据集、网络连接或无法序列化的对象呢?对于持续进行的网络请求等操作呢?Bundle 方法在这些情况下会失效,原因既有大小限制,也有序列化/反序列化的高昂开销。
ViewModel 通过提供一个生命周期感知容器来解决这个问题,该容器通过保留对象模式(而非序列化)来应对配置更改。
ViewModelStore:保留机制
ViewModel 配置变更后仍能保留的核心是 ViewModelStore,它是一个简单的键值存储,用于保存 ViewModel 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 ()
}
@RestrictTo ( RestrictTo . Scope . LIBRARY_GROUP )
public operator fun get ( key : String ): ViewModel ? {
return map [ key ]
}
@RestrictTo ( RestrictTo . Scope . LIBRARY_GROUP )
public fun keys (): Set < String > {
return HashSet ( map . keys )
}
public fun clear () {
for ( vm in map . values ) {
vm . clear ()
}
map . clear ()
}
}
实现非常简单,就是一个 MutableMap<String, ViewModel>。关键不在于存储本身,而在于存储的保留方式。
键替换行为
请注意 put 方法的行为:
1
2
3
4
public fun put ( key : String , viewModel : ViewModel ) {
val oldViewModel = map . put ( key , viewModel )
oldViewModel ?. clear ()
}
如果已存在具有相同键的 ViewModel,则旧的 ViewModel 会被立即清除。这确保了在替换 ViewModel 时能够正确清理。你可能想知道这种情况何时发生,它发生在你请求具有相同键但类型不同的 ViewModel 时:
1
2
3
4
5
6
7
// First request creates TestViewModel1 with key "my_key"
val vm1 : TestViewModel1 = viewModelProvider [ "my_key" , TestViewModel1 :: class ]
// Second request with same key but different type
val vm2 : TestViewModel2 = viewModelProvider [ "my_key" , TestViewModel2 :: class ]
// vm1.onCleared() has been called, vm1 is no longer valid
此行为已在测试套件中验证:
1
2
3
4
5
6
7
8
9
@Test
fun twoViewModelsWithSameKey () {
val key = "the_key"
val vm1 = viewModelProvider [ key , TestViewModel1 :: class ]
assertThat ( vm1 . cleared ). isFalse ()
val vw2 = viewModelProvider [ key , TestViewModel2 :: class ]
assertThat ( vw2 ). isNotNull ()
assertThat ( vm1 . cleared ). isTrue ()
}
ViewModelStoreOwner 契约
ViewModelStoreOwner 接口定义了谁拥有该存储:
1
2
3
public interface ViewModelStoreOwner {
public val viewModelStore : ViewModelStore
}
ComponentActivity、Fragment 和 NavBackStackEntry 都实现了这个简单的接口。所有者的职责有两方面:
在配置更改后保留存储 :存储必须在 Activity 重建后仍然存在。
在真正完成后清除存储 :当所有者被销毁而未重建时,调用 ViewModelStore.clear()。
对于 Activity 而言,这通常使用 NonConfigurationInstances 来实现,这是一种特殊的机制,允许对象在配置更改后仍然存在。Activity 框架在 onRetainNonConfigurationInstance() 期间保留这些对象,并在 getLastNonConfigurationInstance() 中恢复它们。
为什么简单的映射有效
你可能期望使用复杂的缓存机制,但简单的 MutableMap 就足够了,原因如下:
大小有限 :每个屏幕上的 ViewModel 数量很少(通常为 1-5 个)。
字符串键 :键由类名生成,使得查找复杂度为 O(1),并且哈希分布良好。
无需驱逐 :ViewModel 仅在显式请求或所有者被销毁时才会被清除。
线程安全 :访问在 ViewModelProvider 级别同步。
ViewModelProvider:编排层
ViewModelProvider 是获取 ViewModel 实例的主要 API。它负责编排 store、factory 和创建 extras 之间的交互:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public actual open class ViewModelProvider
private constructor ( private val impl : ViewModelProviderImpl ) {
public constructor (
store : ViewModelStore ,
factory : Factory ,
defaultCreationExtras : CreationExtras = CreationExtras . Empty ,
) : this ( ViewModelProviderImpl ( store , factory , defaultCreationExtras ))
public constructor (
owner : ViewModelStoreOwner
) : this (
store = owner . viewModelStore ,
factory = ViewModelProviders . getDefaultFactory ( owner ),
defaultCreationExtras = ViewModelProviders . getDefaultCreationExtras ( owner ),
)
@MainThread
public actual operator fun < T : ViewModel > get ( modelClass : KClass < T >): T =
impl . getViewModel ( modelClass )
@MainThread
public actual operator fun < T : ViewModel > get ( key : String , modelClass : KClass < T >): T =
impl . getViewModel ( modelClass , key )
}
多平台抽象
请注意 ViewModelProviderImpl 委托。ViewModel 库是一个 Kotlin 多平台库,支持 JVM、Android、iOS 和其他平台。Kotlin 多平台目前还不支持带有默认实现的 expect 类,因此通用逻辑被提取到内部实现类中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
internal class ViewModelProviderImpl (
private val store : ViewModelStore ,
private val factory : ViewModelProvider . Factory ,
private val defaultExtras : CreationExtras ,
) {
private val lock = SynchronizedObject ()
@Suppress ( "UNCHECKED_CAST" )
internal fun < T : ViewModel > getViewModel (
modelClass : KClass < T >,
key : String = ViewModelProviders . getDefaultKey ( modelClass ),
): T {
return synchronized ( lock ) {
val viewModel = store [ key ]
if ( modelClass . isInstance ( viewModel )) {
if ( factory is ViewModelProvider . OnRequeryFactory ) {
factory . onRequery ( viewModel !! )
}
return @synchronized viewModel as T
}
val modelExtras = MutableCreationExtras ( defaultExtras )
modelExtras [ ViewModelProvider . VIEW_MODEL_KEY ] = key
return @synchronized createViewModel ( factory , modelClass , modelExtras ). also { vm ->
store . put ( key , vm )
}
}
}
}
获取或创建模式
getViewModel 方法实现了经典的获取或创建模式:
生成键 :默认键基于类的规范名称。
检查缓存 :通过键查找已存在的 ViewModel。
类型检查 :验证缓存实例的类型是否正确。
返回缓存 :如果有效,则返回缓存的实例。
创建新实例 :如果未找到或类型错误,则通过工厂模式创建新实例。
存储 :将新实例放入存储中。
使用同步访问确保线程安全
synchronized(lock) 代码块确保线程安全访问。虽然 ViewModel 的访问通常来自主线程(如 @MainThread 所示),但同步机制可以防止后台线程访问 ViewModel 等极端情况,尤其是在测试场景或使用 viewModelScope 时。
OnRequeryFactory 回调
OnRequeryFactory 是一种特殊的机制,用于在检索缓存的 ViewModel 时执行操作的工厂:
1
2
3
4
@RestrictTo ( RestrictTo . Scope . LIBRARY_GROUP )
public actual open class OnRequeryFactory {
public actual open fun onRequery ( viewModel : ViewModel ) {}
}
SavedStateHandle 内部使用此机制在配置更改后将 ViewModel 与当前的 SavedStateRegistry 重新连接。当从缓存中检索 ViewModel 时,会调用工厂的 onRequery 方法,从而更新可能已更改的引用。
从类名生成键
默认的键生成机制可防止意外冲突:
1
2
3
4
5
6
7
internal fun < T : ViewModel > getDefaultKey ( modelClass : KClass < T >): String {
val canonicalName =
requireNotNull ( modelClass . canonicalName ) {
"Local and anonymous classes can not be ViewModels"
}
return "$VIEW_MODEL_PROVIDER_DEFAULT_KEY:$canonicalName"
}
前缀 "androidx.lifecycle.ViewModelProvider.DefaultKey:" 可确保键不会与用户提供的自定义键冲突。规范名称要求也解释了为什么本地类和匿名类不能是 ViewModel,因为它们没有规范名称:
1
2
3
4
5
6
7
8
9
10
11
12
@Test
fun localViewModel () {
class LocalViewModel : ViewModel ()
try {
viewModelProvider [ LocalViewModel :: class ]
fail ( "Expected `IllegalArgumentException`" )
} catch ( e : IllegalArgumentException ) {
assertThat ( e )
. hasMessageThat ()
. contains ( "Local and anonymous classes can not be ViewModels" )
}
}
工厂模式:灵活实例化
ViewModelProvider.Factory 是创建 ViewModel 实例的机制:
1
2
3
4
5
6
7
8
9
10
public actual interface Factory {
public fun < T : ViewModel > create ( modelClass : Class < T >): T =
ViewModelProviders . unsupportedCreateViewModel ()
public fun < T : ViewModel > create ( modelClass : Class < T >, extras : CreationExtras ): T =
create ( modelClass )
public actual fun < T : ViewModel > create ( modelClass : KClass < T >, extras : CreationExtras ): T =
create ( modelClass . java , extras )
}
方法重载链
工厂接口有三个 create 方法变体,形成一个链:
create(KClass<T>, CreationExtras):Kotlin 优先 API,委托给 Java 变体
create(Class<T>, CreationExtras):主要实现点,默认为无附加组件的变体
create(Class<T>):传统 API,默认抛出异常
此链在保持向后兼容性的同时,也鼓励使用基于 CreationExtras 的现代方法。
NewInstanceFactory:基于反射的创建
最简单的工厂使用反射来创建带有无参构造函数的 ViewModel:
1
2
3
4
public open class NewInstanceFactory : Factory {
public override fun < T : ViewModel > create ( modelClass : Class < T >): T =
JvmViewModelProviders . createViewModel ( modelClass )
}
JVM 实现使用反射并进行了细致的错误处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
internal object JvmViewModelProviders {
fun < T : ViewModel > createViewModel ( modelClass : Class < T >): T {
val constructor =
try {
modelClass . getDeclaredConstructor ()
} catch ( e : NoSuchMethodException ) {
throw RuntimeException ( "Cannot create an instance of $modelClass" , e )
}
// Enforce public constructor for consistent behavior
if (! Modifier . isPublic ( constructor . modifiers )) {
throw RuntimeException ( "Cannot create an instance of $modelClass" )
}
return try {
constructor . newInstance ()
} catch ( e : InstantiationException ) {
throw RuntimeException ( "Cannot create an instance of $modelClass" , e )
} catch ( e : IllegalAccessException ) {
throw RuntimeException ( "Cannot create an instance of $modelClass" , e )
}
}
}
公共修饰符检查对于测试一致性至关重要。在插桩测试中,R8 可能会移除访问修饰符,使私有构造函数可访问。JVM 测试严格执行访问限制。此显式检查可确保跨测试环境的行为一致性。
AndroidViewModelFactory:应用感知创建
AndroidViewModelFactory 继承自 NewInstanceFactory 以支持 AndroidViewModel,后者需要一个 Application 参数。该工厂在保持向后兼容性的同时,也采用了现代的 CreationExtras 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public open class AndroidViewModelFactory
private constructor (
private val application : Application ?,
@Suppress ( "UNUSED_PARAMETER" ) unused : Int ,
) : NewInstanceFactory () {
override fun < T : ViewModel > create ( modelClass : Class < T >, extras : CreationExtras ): T {
return if ( application != null ) {
create ( modelClass )
} else {
val application = extras [ APPLICATION_KEY ]
if ( application != null ) {
create ( modelClass , application )
} else {
if ( AndroidViewModel :: class . java . isAssignableFrom ( modelClass )) {
throw IllegalArgumentException (
"CreationExtras must have an application by `APPLICATION_KEY`"
)
}
super . create ( modelClass )
}
}
}
private fun < T : ViewModel > create ( modelClass : Class < T >, app : Application ): T {
return if ( AndroidViewModel :: class . java . isAssignableFrom ( modelClass )) {
try {
modelClass . getConstructor ( Application :: class . java ). newInstance ( app )
} catch ( e : NoSuchMethodException ) {
throw RuntimeException ( "Cannot create an instance of $modelClass" , e )
}
} else super . create ( modelClass )
}
}
该工厂实现了级联解析策略:首先检查是否将 Application 传递给了构造函数(传统方法),然后通过 APPLICATION_KEY 在 CreationExtras 中查找(现代无状态方法),最后对于常规 ViewModel 回退到 NewInstanceFactory,或者如果 AndroidViewModel 没有可用的 Application,则抛出异常。
私有构造函数中的 unused: Int 参数是一个巧妙的技巧,用于区分重载构造函数,因为如果使用 null 调用 constructor(),编译器将无法区分 constructor(application: Application?)。
实例化使用了反射(modelClass.getConstructor(Application::class.java).newInstance(app)),这意味着你的 AndroidViewModel 子类必须有一个接受且仅接受一个 Application 参数的构造函数。对于其他依赖项,你需要自定义工厂或依赖注入框架,例如 Hilt。
InitializerViewModelFactory:基于 Lambda 的创建
对于具有自定义依赖项的 ViewModel,InitializerViewModelFactory 提供了一种基于 DSL 的方法,可以减少样板代码:
1
2
3
4
val factory = viewModelFactory {
initializer { MyViewModel ( get ( MY_KEY )) }
initializer { AnotherViewModel ( get ( ANOTHER_KEY )) }
}
该 DSL 由一个构建器类驱动,该构建器类收集初始化 Lambda 表达式,其中每个初始化器将一个 KClass 与其创建 Lambda 表达式配对:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ViewModelFactoryDsl
public class InitializerViewModelFactoryBuilder public constructor () {
private val initializers = mutableMapOf < KClass <*>, ViewModelInitializer <*>>()
public fun < T : ViewModel > addInitializer (
clazz : KClass < T >,
initializer : CreationExtras .() -> T ,
) {
require ( clazz ! in initializers ) {
"A `initializer` with the same `clazz` has already been added: ${clazz.canonicalName}."
}
initializers [ clazz ] = ViewModelInitializer ( clazz , initializer )
}
public fun build (): ViewModelProvider . Factory =
ViewModelProviders . createInitializerFactory ( initializers . values )
}
@ViewModelFactoryDsl 注解是一个 DSL 标记,可防止嵌套的构建器作用域意外访问外部作用域的方法。初始化 Lambda 表达式接收 CreationExtras 作为其接收器,允许通过 get() 直接访问额外内容。
在创建时,工厂会通过初始化器进行线性搜索,以找到匹配的类:
1
2
3
4
5
6
7
8
9
10
11
internal fun < VM : ViewModel > createViewModelFromInitializers (
modelClass : KClass < VM >,
extras : CreationExtras ,
vararg initializers : ViewModelInitializer <*>,
): VM {
val viewModel =
initializers . firstOrNull { it . clazz == modelClass } ?. initializer ?. invoke ( extras ) as VM ?
return requireNotNull ( viewModel ) {
"No initializer set for given class ${modelClass.canonicalName}"
}
}
线性搜索是可以接受的,因为每个工厂创建的 ViewModel 数量通常很少(1-5 个),而且 ViewModel 的创建频率很低(每个生命周期一次)。
CreationExtras:无状态工厂配置
CreationExtras 是一个类型安全的键值容器,用于在不使工厂成为有状态的情况下向其传递配置。工厂不再通过构造函数注入来持有依赖项,而是在创建时传递依赖项:
1
2
3
4
5
6
7
8
9
10
11
public abstract class CreationExtras internal constructor () {
internal val extras : MutableMap < Key <*>, Any ?> = mutableMapOf ()
public interface Key < T >
public abstract operator fun < T > get ( key : Key < T >): T ?
public object Empty : CreationExtras () {
override fun < T > get ( key : Key < T >): T ? = null
}
}
类型安全的键
每个键都由其关联的值类型参数化,从而提供编译时类型安全。当你使用 extras[APPLICATION_KEY] 获取值时,返回类型会自动为 Application?:
1
2
val APPLICATION_KEY : Key < Application > = CreationExtras . Companion . Key ()
val VIEW_MODEL_KEY : Key < String > = CreationExtras . Companion . Key ()
键的创建使用了一个内联函数,该函数创建一个实现 Key 接口的新匿名对象。由于每个键都是一个唯一的对象实例,因此键之间通过标识(===)进行比较,即使两个键具有相同的类型参数,也不会发生意外冲突:
1
2
@JvmStatic
public inline fun < reified T > Key (): Key < T > = object : Key < T > {}
用于修改的 MutableCreationExtras
基类 CreationExtras 是只读的,而 MutableCreationExtras 允许添加条目。这种分离遵循 Kotlin 的集合设计理念(例如 List 与 MutableList),防止工厂意外修改共享的 extras:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MutableCreationExtras
public constructor ( initialExtras : CreationExtras = Empty ) : CreationExtras () {
init {
extras += initialExtras . extras
}
public operator fun < T > set ( key : Key < T >, t : T ) {
extras [ key ] = t
}
@Suppress ( "UNCHECKED_CAST" )
public override fun < T > get ( key : Key < T >): T ? = extras [ key ] as T ?
}
与 ViewModelProvider 集成
ViewModelProvider 会在调用工厂之前自动将 ViewModel 的键添加到 extras 中,这对于像 SavedStateHandle 这样需要键来正确限定持久化范围的功能至关重要:
1
2
3
4
val modelExtras = MutableCreationExtras ( defaultExtras )
modelExtras [ ViewModelProvider . VIEW_MODEL_KEY ] = key
return @synchronized createViewModel ( factory , modelClass , modelExtras )
HasDefaultViewModelProviderFactory
ViewModelStoreOwner 的实现可以通过 HasDefaultViewModelProviderFactory 接口提供默认的工厂和 extras。 ComponentActivity 和 Fragment 都实现了此接口,并提供了支持 SavedStateHandle 且预先填充了 APPLICATION_KEY 的工厂:
1
2
3
4
5
6
public interface HasDefaultViewModelProviderFactory {
public val defaultViewModelProviderFactory : ViewModelProvider . Factory
public val defaultViewModelCreationExtras : CreationExtras
get () = CreationExtras . Empty
}
ComponentActivity 和 Fragment 都实现了此接口,并提供了支持 SavedStateHandle 和正确的 CreationExtras 且预先填充了 APPLICATION_KEY 的工厂。
从 ViewModelStoreOwner 创建 ViewModelProvider 时,会自动使用以下默认值:
1
2
3
4
5
6
7
8
9
10
11
12
13
internal fun getDefaultFactory ( owner : ViewModelStoreOwner ): ViewModelProvider . Factory =
if ( owner is HasDefaultViewModelProviderFactory ) {
owner . defaultViewModelProviderFactory
} else {
DefaultViewModelProviderFactory
}
internal fun getDefaultCreationExtras ( owner : ViewModelStoreOwner ): CreationExtras =
if ( owner is HasDefaultViewModelProviderFactory ) {
owner . defaultViewModelCreationExtras
} else {
CreationExtras . Empty
}
这种模式支持渐进增强:简单的 ViewModel 使用默认工厂,而复杂的 ViewModel 可以通过 extras 获取丰富的配置,而无需更改获取 ViewModelProvider 的方式。
ViewModel 类:资源管理
ViewModel 类通过 AutoCloseable 模式管理资源生命周期。expect 关键字表明这是一个 Kotlin 多平台声明,每个平台都提供自己的 actual 实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public expect abstract class ViewModel {
public constructor ()
public constructor ( viewModelScope : CoroutineScope )
public constructor ( vararg closeables : AutoCloseable )
public constructor ( viewModelScope : CoroutineScope , vararg closeables : AutoCloseable )
protected open fun onCleared ()
@MainThread internal fun clear ()
public fun addCloseable ( key : String , closeable : AutoCloseable )
public open fun addCloseable ( closeable : AutoCloseable )
public fun < T : AutoCloseable > getCloseable ( key : String ): T ?
}
ViewModelImpl 内部实现
实际实现委托给 ViewModelImpl,遵循多平台模式,将通用逻辑提取到内部类中。isCleared 上的 @Volatile 注解确保跨线程可见,这对于清除后的保护机制至关重要:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal class ViewModelImpl {
private val lock = SynchronizedObject ()
private val keyToCloseables = mutableMapOf < String , AutoCloseable >()
private val closeables = mutableSetOf < AutoCloseable >()
@Volatile private var isCleared = false
constructor ()
constructor ( viewModelScope : CoroutineScope ) {
addCloseable ( VIEW_MODEL_SCOPE_KEY , viewModelScope . asCloseable ())
}
constructor ( vararg closeables : AutoCloseable ) {
this . closeables += closeables
}
constructor ( viewModelScope : CoroutineScope , vararg closeables : AutoCloseable ) {
addCloseable ( VIEW_MODEL_SCOPE_KEY , viewModelScope . asCloseable ())
this . closeables += closeables
}
}
两层可关闭存储
该实现维护两个集合:keyToCloseables 用于注册后需要检索的资源(例如 viewModelScope),以及 closeables 用于即用即弃的清理。使用 Set 来管理匿名可关闭资源可以防止重复注册导致双重关闭。
清除顺序
当 ViewModel 被清除时,资源会按照特定的顺序进行清理。isCleared = true 标志会在同步代码块之前设置,以防止并发的 addCloseable 调用添加会被遗漏的资源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@MainThread
fun clear () {
if ( isCleared ) return
isCleared = true
synchronized ( lock ) {
for ( closeable in keyToCloseables . values ) {
closeWithRuntimeException ( closeable )
}
for ( closeable in closeables ) {
closeWithRuntimeException ( closeable )
}
// Clear only resources without keys to prevent accidental recreation
closeables . clear ()
}
}
匿名 closeables 集合会被清除,但 keyToCloseables 会被有意保留。这可以防止在清除后代码访问 viewModelScope 等资源时意外地重新创建它们。
清除后保护
在清除 ViewModel 后添加可关闭对象会立即将其关闭,从而防止在协程仍在运行时 ViewModel 被清除时出现资源泄漏:
1
2
3
4
5
6
7
8
9
fun addCloseable ( key : String , closeable : AutoCloseable ) {
if ( isCleared ) {
closeWithRuntimeException ( closeable )
return
}
val oldCloseable = synchronized ( lock ) { keyToCloseables . put ( key , closeable ) }
closeWithRuntimeException ( oldCloseable )
}
此外,请注意,添加键值可关闭对象时,任何具有相同键值的现有可关闭对象都会自动关闭,从而支持替换模式。
用于模拟的可空实现
JVM 实现提供了一个可空的 impl 对象,以支持模拟框架。当你模拟 ViewModel 时,模拟对象不会调用真正的构造函数,因此 impl 对象永远不会被初始化。如果没有可空类型和安全调用(impl?.clear()),测试会因 NullPointerException 而崩溃:
1
2
3
4
5
6
7
8
9
10
11
12
13
public actual abstract class ViewModel {
private val impl : ViewModelImpl ?
public actual constructor () {
impl = ViewModelImpl ()
}
@MainThread
internal actual fun clear () {
impl ?. clear ()
onCleared ()
}
}
viewModelScope:协程集成
viewModelScope 扩展属性提供了一个生命周期感知的 CoroutineScope,当 ViewModel 被清除时,该作用域会自动取消:
1
2
3
4
5
6
7
8
public val ViewModel . viewModelScope : CoroutineScope
get () =
synchronized ( VIEW_MODEL_SCOPE_LOCK ) {
getCloseable ( VIEW_MODEL_SCOPE_KEY )
?: createViewModelScope (). also { scope ->
addCloseable ( VIEW_MODEL_SCOPE_KEY , scope )
}
}
使用键控存储的延迟创建
作用域在首次访问时延迟创建,并使用键控可关闭机制进行存储。键名故意设置得非常详细,以避免与用户自定义的键发生冲突:
1
2
internal const val VIEW_MODEL_SCOPE_KEY =
"androidx.lifecycle.viewmodel.internal.ViewModelCoroutineScope.JOB_KEY"
CloseableCoroutineScope
协程和可关闭系统之间的桥梁是 CloseableCoroutineScope,它同时实现了 CoroutineScope 和 AutoCloseable 接口。在 ViewModel 清空期间调用 close() 时,所有正在运行的协程都会被取消:
1
2
3
4
5
6
7
internal class CloseableCoroutineScope ( override val coroutineContext : CoroutineContext ) :
AutoCloseable , CoroutineScope {
constructor ( coroutineScope : CoroutineScope ) : this ( coroutineScope . coroutineContext )
override fun close () = coroutineContext . cancel ()
}
平台感知调度器选择
作为 Kotlin 多平台库,ViewModel 在没有主线程概念的平台上也能工作,因为它会回退到 EmptyCoroutineContext。请注意,这里使用的是 Dispatchers.Main.immediate 而不是 Dispatchers.Main,这样可以避免在已经在主线程上运行时进行不必要的重新调度:
1
2
3
4
5
6
7
8
9
10
11
12
13
internal fun createViewModelScope (): CloseableCoroutineScope {
val dispatcher =
try {
Dispatchers . Main . immediate
} catch ( _ : NotImplementedError ) {
// In Native environments where `Dispatchers.Main` might not exist (e.g., Linux)
EmptyCoroutineContext
} catch ( _ : IllegalStateException ) {
// In JVM Desktop environments where `Dispatchers.Main` might not exist (e.g., Swing)
EmptyCoroutineContext
}
return CloseableCoroutineScope ( coroutineContext = dispatcher + SupervisorJob ())
}
SupervisorJob 实现子协程独立失败
该作用域使用 SupervisorJob(),允许子协程独立失败。而使用普通的 Job 时,如果一个子协程失败,它会取消所有同级协程。这种设计符合 UI 应用程序的预期,即失败的网络请求不应该取消正在进行的数据库操作。
ViewModelLazy:Kotlin 属性委托
viewModels() 委托使用了 ViewModelLazy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ViewModelLazy < VM : ViewModel >
@JvmOverloads
constructor (
private val viewModelClass : KClass < VM >,
private val storeProducer : () -> ViewModelStore ,
private val factoryProducer : () -> ViewModelProvider . Factory ,
private val extrasProducer : () -> CreationExtras = { CreationExtras . Empty },
) : Lazy < VM > {
private var cached : VM ? = null
override val value : VM
get () {
val viewModel = cached
return if ( viewModel == null ) {
val store = storeProducer ()
val factory = factoryProducer ()
val extras = extrasProducer ()
ViewModelProvider . create ( store , factory , extras ). get ( viewModelClass ). also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized (): Boolean = cached != null
}
双重缓存架构
惰性委托会在首次访问后将 ViewModel 引用缓存到本地。这是一种双重缓存模式:
ViewModelStore 缓存 :规范缓存,不受配置更改的影响。
ViewModelLazy 缓存 :本地缓存,用于避免重复创建 ViewModelProvider。
本地缓存是一种优化,创建 ViewModelProvider 并查找 ViewModel 的开销很小,但缓存可以避免后续访问中哪怕是这种微小的开销。
用于延迟访问的 Lambda 生产者
所有三个生产者(store、factory、extras)都是 Lambda 表达式,允许延迟求值:
1
2
private val viewModel by viewModels { MyViewModelFactory () }
// Factory is only created when viewModel is first accessed
这对于 Fragment 尤其重要,因为在属性初始化期间 Activity 可能不可用。
Android 特有:AGP 代码脱糖兼容性
Android 有一个特殊的兼容层,用于处理混合使用针对不同 ViewModel 版本编译的库时出现的 AGP(Android Gradle 插件)代码脱糖问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal actual fun < VM : ViewModel > createViewModel (
factory : ViewModelProvider . Factory ,
modelClass : KClass < VM >,
extras : CreationExtras ,
): VM {
return try {
factory . create ( modelClass , extras )
} catch ( e : AbstractMethodError ) {
try {
factory . create ( modelClass . java , extras )
} catch ( e : AbstractMethodError ) {
factory . create ( modelClass . java )
}
}
}
这一系列 try-catch 代码块用于处理使用旧版本 ViewModel 编译的工厂缺少较新版本的 create 方法的情况。当运行时调用编译后的字节码中不存在的方法时,就会出现 AbstractMethodError 错误,这种情况通常发生在旧工厂只有 create(Class<T>) 方法,而运行时需要 create(Class<T>, CreationExtras) 方法时。这种防御性级联机制能够优雅地降级到方法变体,确保 ViewModel 创建功能不受库版本不匹配的影响。
性能特性和设计权衡
ViewModel 系统在设计上做出了一些权衡,以平衡正确性、灵活性和性能。
同步开销
ViewModelProviderImpl 和 ViewModelImpl 都使用同步块来实现线程安全访问:
1
2
3
4
synchronized ( lock ) {
val viewModel = store [ key ]
// ...
}
由于 ViewModel 访问通常发生在主线程上,临界区很短(简单的 map 操作),并且锁争用很少发生,因此这种同步开销非常小。该设计优先考虑正确性而非微优化,因为罕见的竞态条件代价远高于几乎察觉不到的同步开销。
反射开销
NewInstanceFactory 使用 Java 反射来动态实例化 ViewModel 类:
1
modelClass . getDeclaredConstructor (). newInstance ()
由于运行时类型检查和动态方法解析,反射比直接构造函数调用要慢。但是,这种开销可以忽略不计,因为 ViewModel 的创建频率很低(每个生命周期一次),而且无需编译时信息即可实例化任何 ViewModel 子类的灵活性远远超过了这点微小的开销。
基于映射的存储
使用 MutableMap 作为 ViewModelStore 可以提供 O(1) 的查找时间,但清除操作的时间复杂度为 O(n),因为每个 ViewModel 都必须单独清除:
1
2
3
4
5
6
public fun clear () {
for ( vm in map . values ) {
vm . clear ()
}
map . clear ()
}
由于 ViewModelStore 通常只包含 1-5 个 ViewModel,因此线性时间的清除操作实际上并无影响。使用标准 HashMap 的简洁性使得代码更易于理解和维护,对于这种有限的用例而言,其价值远大于理论上的优化。
用于模拟的可空实现
JVM ViewModel 中的可空 impl 会在每个操作中添加空值检查:
这种微小的开销可以实现正确的模拟行为。当模拟框架创建 ViewModel 模拟对象时,它们会绕过真正的构造函数,导致 impl 未初始化。可空类型可以防止测试中出现 NullPointerException,这是一个值得的权衡。
实际应用模式:理解 ViewModel 的使用
理解其内部机制有助于你在实际应用中识别模式和反模式。
模式 1:作用域 ViewModel 共享
可以使用 Activity 的 ViewModelStore 而不是每个 Fragment 自己的存储来在 Fragment 之间共享 ViewModel:
1
2
3
4
5
6
7
class FragmentA : Fragment () {
private val sharedViewModel : SharedViewModel by activityViewModels ()
}
class FragmentB : Fragment () {
private val sharedViewModel : SharedViewModel by activityViewModels ()
}
两个 Fragment 会获得相同的 ViewModel 实例,因为它们使用相同的 ViewModelStore(Activity 的)。这使得兄弟 Fragment 可以通过共享状态进行通信。内部机制:
1
2
3
4
5
// activityViewModels() uses:
ViewModelProvider (
store = requireActivity (). viewModelStore , // Same store for both fragments
factory = ...,
)
模式 2:自定义键用于多个实例
你可以通过提供自定义键来拥有同一个 ViewModel 类的多个实例:
1
2
val viewModel1 : MyViewModel = viewModelProvider [ "user_1" , MyViewModel :: class ]
val viewModel2 : MyViewModel = viewModelProvider [ "user_2" , MyViewModel :: class ]
该键包含自定义字符串,从而在 ViewModelStore 中创建单独的缓存条目。这对于同时管理多个用户配置文件或聊天对话等场景非常有用。
模式 3:使用 CreationExtras 进行工厂注入
现代工厂是无状态的,在创建时通过 CreationExtras 接收所有依赖项:
1
2
3
4
5
6
7
class MyViewModelFactory : ViewModelProvider . Factory {
override fun < T : ViewModel > create ( modelClass : KClass < T >, extras : CreationExtras ): T {
val repository = extras [ REPOSITORY_KEY ] !!
val userId = extras [ USER_ID_KEY ] !!
return MyViewModel ( repository , userId ) as T
}
}
这使得工厂可以作为单例,同时每次调用配置都不同,从而避免为每个具有不同依赖项的 ViewModel 创建新的工厂实例。
反模式:在 ViewModel 中访问 Context
1
2
3
4
5
6
7
8
9
10
// Bad: Leaks Activity context
class MyViewModel ( private val context : Context ) : ViewModel ()
// Good: Use Application context via AndroidViewModel
class MyViewModel ( application : Application ) : AndroidViewModel ( application )
// Better: Don't hold Context at all, pass it to methods
class MyViewModel : ViewModel () {
fun loadData ( context : Context ) { ... }
}
第一种方法可能会导致 Activity 泄漏,因为 ViewModel 在持有已销毁 Activity 的引用时,配置更改仍然存在。请使用 AndroidViewModel 来获取 Application 上下文,或者在需要时将 Context 传递给各个方法。
反模式:在 onCleared 中阻塞
你知道 onCleared() 是在主线程上调用的吗?诸如网络关闭或数据库清理之类的阻塞操作应该使用协程的 addCloseable 方法卸载到后台线程:
1
2
3
4
5
6
7
8
9
10
11
// Bad: Blocking call in onCleared
override fun onCleared () {
networkClient . shutdown () // May block the main thread until completing the shutdown process!
}
// Good: Use addCloseable for managed cleanup
init {
addCloseable {
scope . launch { networkClient . shutdown () }
}
}
结论
Jetpack ViewModel 的内部机制展现了一个精心设计的生命周期感知状态管理系统。ViewModelStore 通过保留映射提供简单而有效的缓存,而 ViewModelProvider 则通过线程安全的访问和灵活的工厂支持来协调创建过程。CreationExtras 通过外部化配置来实现无状态工厂,而 AutoCloseable 集成则确保了资源的正确清理。
多平台架构将通用逻辑提取到内部实现类中,使该库能够支持 JVM、Android、iOS 和其他平台。viewModelScope 集成通过 closeable 机制提供自动协程取消功能,并为没有主线程的环境提供平台感知的调度器选择。
如果你想掌握最新的技能、新闻、技术文章、面试题和实用代码技巧,请查看Dove Letter 。想要更深入地了解面试准备,千万不要错过终极 Android 面试指南:Manifest Android Interview 。