ViewModel 底层原理

ViewModel 是Jetpack Architecture 下 MVI/MVVM 架构的核心组件。今天来深入 ViewModel 的跨配置存活和生命周期的底层原理。

我们可以先定义一个继承自 ViewModel 的 MyViewModel 类:

java 复制代码
public class MyViewModel extends ViewModel {
    private final MutableLiveData<String> _userName = new MutableLiveData<>();

    public MyViewModel() {
        _userName.setValue("张三");
    }
    
    public LiveData<String> get_userName() {
        return _userName;
    }

    public void update_userName(String newValue) {
        _userName.setValue(newValue);
    }	
}

这次我们不使用 ViewModelProvider,而是直接实例化:

java 复制代码
MyViewModel myVm = new MyViewModel();

然后订阅该状态:

java 复制代码
myVm.get_userName().observe(this, newName -> tv.setText(newName));
// 或不采用 lambda
myVm.get_userName().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String s) {
        tv.setText(s);
    }
});

当你调用 update_userName() 将 _userName 的值更新为 "李四",Observer 观察到值发生改变,立即触发回调更新 TextView。但如果此时你旋转屏幕,你会发现 TextView 的值又变回了 "张三"。这是因为我们绕过了 ViewModelProvider,没有用到它背后的存储机制。严格意义上讲,ViewModel 这个类本身好像并没有保持状态的能力?

其实,它也没有 Lifecycle,不知道 Activity 何时重建,不具备任何跨配置变更存活的魔法。ViewModel 只是一个数据容器,负责把 UI 需要的数据从 Activity 中解耦。

保持状态的类是 ViewModelStore,所以正如 Android 开发者文档 About ViewModel 所说:

实例化 ViewModel 时,您会向其传递实现 ViewModelStoreOwner 接口的对象。它可能是 Navigation 目的地、Navigation 图表、activity 或实现接口的任何其他类型。您还可以使用 rememberViewModelStoreOwner API 将 ViewModel 直接限定到可组合项。然后,ViewModel 的作用域将限定为 Lifecycle 的 ViewModelStoreOwner。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失 (例如,当可组合项所有者退出组合时)。

ViewModelStoreOwner 接口定义了 viewModelStore 变量:

kotlin 复制代码
// ViewModelStoreOwner 源码
public interface ViewModelStoreOwner {
    /** The owned [ViewModelStore] */
    public val viewModelStore: ViewModelStore
}

我们使用 ViewModelProvider 创建 MyViewModel,传入当前 Activity,而 Activity 的基类 ComponentActivity 实现了 ViewModelStoreOwner 接口:

java 复制代码
MyViewModel myVm;
@Override
protected void onCreate(Bundle saveInstanceState) {
    myVm = new ViewModelProvider(this).get(MyViewModel.class);
}

ComponentActivity 对 viewModelStore 的实现如下:

kotlin 复制代码
override val viewModelStore: ViewModelStore
    get() {
        checkNotNull(application) {
            ("Your activity is not yet attached to the " +
                "Application instance. You can't request ViewModel before onCreate call.")
        }
        ensureViewModelStore()
        return _viewModelStore!!
    }

private fun ensureViewModelStore() {
    if (_viewModelStore == null) {
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            // 从 NonConfigurationInstances 中恢复 ViewModelStore
            _viewModelStore = nc.viewModelStore
        }
        if (_viewModelStore == null) {
            _viewModelStore = ViewModelStore()
        }
    }
}

这里就是 ViewModel 旋转屏幕状态不丢失的核心实现。可以看到在 Activity 绑定之前使用 ViewModelProvider(this).get(MyViewModel.class) 会打印错误信息。

  • 屏幕旋转后 Activity 重建,_viewModelStore 会被重置,但 nc != null → 旧的 ViewModelStore 从 NonConfigurationInstances 中恢复,里面保存的所有 ViewModel 实例都还在。
  • 首次启动 Activity 时 nc == null,然后创建一个空的 ViewModelStore,后续 ViewModel 会被创建并存入。

屏幕旋转时 Activity 会先销毁,系统在销毁前会调用 Activity.onRetainNonConfigurationInstance(),ComponentActivity 重写了该方法:

kotlin 复制代码
@Suppress("deprecation")
final override fun onRetainNonConfigurationInstance(): Any? {
    // Maintain backward compatibility.
    val custom = onRetainCustomNonConfigurationInstance()
    var viewModelStore = _viewModelStore
    if (viewModelStore == null) {
        // 没有调用过 getViewModelStore(),则尝试从上次的 NonConfigurationInstance 中获取
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            viewModelStore = nc.viewModelStore
        }
    }
    if (viewModelStore == null && custom == null) {
        return null
    }
    val nci = NonConfigurationInstances()
    nci.custom = custom
    nci.viewModelStore = viewModelStore
    return nci
}

这个方法会先把当前的 _viewModelStore 取出;如果未创建过(即从未调用 getViewModelStore()),就会尝试从上次的 NonConfigurationInstances 里复用。最后把 viewModelStore 和自定义对象一起打包进新的 NonConfigurationInstances 对象中返回。这个返回的对象会被系统保留,在 Activity 重建后通过 getLastNonConfigurationInstance() 获取,也就是 ensureViewModelStore() 中看到的恢复来源。其中 lastNonConfigurationInstance 其实是 Kotlin 的语法糖,调用的是 Activity 的该方法:

kotlin 复制代码
@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

我们接着来看 ViewModelStore 是怎么持有这些 ViewModel 的,其实很简单,它内部维护了一个 HashMap:

kotlin 复制代码
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]
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    /** Clears internal storage and notifies `ViewModel`s that they are no longer used. */
    public fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

现在我们知道了 ViewModel 只是一个数据容器,真正保证 ViewModel 跨配置存活的类是 ViewModelStore。我们接着来看 ViewModelProvider 是怎么通过 get() 拿到我们想要的 ViewModel 的。

来看源码,ViewModelProvider 采取了代理模式:

kotlin 复制代码
public actual open class ViewModelProvider
private constructor(private val impl: ViewModelProviderImpl)

ViewModelProvider 的两种构造方法最终都会交给私有主构造器去创建 ViewModelProviderImpl:

kotlin 复制代码
@JvmOverloads
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),
)

我们可以看到 ViewModelProvider 其实是把 get() 方法委托给 ViewModelProviderImpl 代理类 impl 去执行。

kotlin 复制代码
@MainThread
public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
    impl.getViewModel(modelClass)

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T = get(modelClass.kotlin)

@MainThread
public actual operator fun <T : ViewModel> get(key: String, modelClass: KClass<T>): T =
    impl.getViewModel(modelClass, key)
    
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T =
    impl.getViewModel(modelClass.kotlin, key)

我们再去看 ViewModelProviderImpl 的具体实现:

kotlin 复制代码
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)
            }
        }
    }
}

internal expect fun <VM : ViewModel> createViewModel(
    factory: ViewModelProvider.Factory,
    modelClass: KClass<VM>,
    extras: CreationExtras,
): VM

这下明了了:第一次调用 ViewModelProvider(owner: ViewModelStoreOwner).get() 会执行 createViewModel()(不同平台有不同实现),并将创建的 ViewModel 存入 ViewModelStore;当配置变更再次调用 get() 时,直接从 store[key] 返回已有实例,彻底避免了数据丢失。


ViewModel 的生命周期

官方文档对 ViewModel 的生命周期是这样描述的:

ViewModel 的生命周期与其作用域直接关联。ViewModel 会一直保留在内存中,直到其作用域 ViewModelStoreOwner 消失。以下上下文中可能会发生这种情况:

  • 对于 activity,是在 activity 完成时。
  • 对于 Navigation 条目,是在 Navigation 条目从返回堆栈中移除时。
  • 对于可组合项,是在可组合项退出组合时。您可以使用 rememberViewModelStoreOwner 将 ViewModel 直接限定到界面的任意部分(例如 Pager 或 LazyList)。

这使得 ViewModels 成为了存储在配置更改后仍然存在的数据的绝佳解决方案。

我们可以在 ComponentActivity 中找到对应的源码实现:

kotlin 复制代码
lifecycle.addObserver(
    LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            // Clear out the available context
            contextAwareHelper.clearAvailableContext()
            // And clear the ViewModelStore
            if (!isChangingConfigurations) {
                viewModelStore.clear()
            }
            reportFullyDrawnExecutor.activityDestroyed()
        }
    }
)

可以看到,在当前 Activity 销毁时它会判断是否是配置发生改变导致的销毁。如果不是,则认为 Activity 是正常结束且不再需要,此时会调用 viewModelStore.clear() 方法,遍历 HashMap 并依次调用所有 ViewModel 的 clear() 方法执行收尾操作,最后调用 map.clear()(具体实现见前面贴出的 ViewModelStore 源码)。

我们再来看 ViewModel 的 clear() 方法:

kotlin 复制代码
@MainThread
internal actual fun clear() {
    impl?.clear()
    onCleared()
}

ViewModel 实际上把一些资源的回收交给了代理类去实现,然后再去执行我们重写的 onCleared() 进行一些没有通过 ViewModelImpl 的 addCloseable() 方法挂载的资源的回收:

kotlin 复制代码
@MainThread
fun clear() {
    if (isCleared) return

    isCleared = true
    synchronized(lock) {
        for (closeable in keyToCloseables.values) {
            closeWithRuntimeException(closeable)
        }
        for (closeable in closeables) {
            closeWithRuntimeException(closeable)
        }
        // 只清空无 key 的资源集合,防止 viewModelScope 等资源被意外重建
        closeables.clear()
    }
}

这个 ViewModelImpl.clear() 的核心工作就是关闭所有通过 addCloseable 添加的、实现了 AutoCloseable 接口的实例(例如 viewModelScope 的协程作用域,以及你手动添加的各种可关闭资源)。关闭顺序为先关有 key 的(keyToCloseables),再关无 key 的(closeables),最后只清空无 key 集合,避免 viewModelScope 等有 key 资源被意外重建。 这里附上ViewModel的生命周期的图, 其实就是上面提到的文档中的图, 点击阅读也能看到:

至此,ViewModel 的整个生存之谜全部解开:

  • ViewModel 本身只是普通数据容器,不具魔法;
  • ViewModelStore 是持有 ViewModel 的 HashMap;
  • NonConfigurationInstances 负责在配置变更时保存和恢复 ViewModelStore;
  • ViewModelProvider 封装了"先从 Store 取,没有再创建"的逻辑;
  • ComponentActivity 利用生命周期在真正销毁时清理 ViewModelStore,防止内存泄漏,而在配置变更时则保留 ViewModelStore,使数据得以存活。
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui