ViewModel 底层原理

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

我们可以先定义一个继承自 ViewModelMyViewModel 类:

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);
}

ComponentActivityviewModelStore 的实现如下:

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 → 旧的 ViewModelStoreNonConfigurationInstances 中恢复,里面保存的所有 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 源码)。

我们再来看 ViewModelclear() 方法:

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

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

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,使数据得以存活。
相关推荐
阿pin2 小时前
Android随笔-APP首次启动流程
android·application·activity
阿pin2 小时前
Android随笔-SELinux是什么?
android·selinux
红糖奶茶2 小时前
设备管理器中Android出现黄色感叹号怎么办? 如何修复?
android
取个名字太难了~2 小时前
从通用到专用:影像 SDK 的场景化封装与垂直行业落地实践
android·数码相机·美颜·相机连接·demu
zakariyaa332 小时前
Android 绘制调度机制
android·gitee
安卓修改大师2 小时前
安卓修改大师Smali语法实战:从零掌握数据类型、判断循环、自定义方法与Toast插桩
android
私人珍藏库2 小时前
[Android] 多开空间-一机多账号+应用一键克隆双开
android·人工智能·智能手机·软件
海兰2 小时前
【SpringBoot 】AOP企业级权限控制方案(二)
android·java·spring boot
阿pin2 小时前
Android随笔-启动Zygote的rc文件是什么?
android·zygote·rc