ViewModel的底层原理:ViewModelStore与跨配置存活

ViewModel 本身只是一个数据容器——真正让它在屏幕旋转后存活的是 ViewModelStore,而 ComponentActivity 通过 NonConfigurationInstances 在重建时恢复整个 Store;其生命周期在 Activity 真正销毁时才结束

ViewModel ViewModelStore ViewModelProvider NonConfigurationInstances Lifecycle Android Jetpack

目录导航(点击跳转)

一、直接实例化 ViewModel:旋转屏幕状态丢失

1绕过 ViewModelProvider 会发生什么

ViewModel 是 Jetpack 组件库 Architecture 分类下的一个组件,是 MVI 和 MVVM 架构的核心。先定义一个继承 ViewModel 的类:

public class MyViewModel extends ViewModel {
    private final MutableLiveData<String> _userName = new MutableLiveData<>();

    public MyViewModel() {
        _userName.setValue("张三");
    }

    public LiveData<String> getUserName() {
        return _userName;
    }

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

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

MyViewModel myVm = new MyViewModel();

// 订阅状态变化
myVm.getUserName().observe(this, newName -> tv.setText(newName));
// 或不采用 lambda
myVm.getUserName().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String s) {
        tv.setText(s);
    }
});

调用 updateUserName("李四") 后,Observer 检测到值变化立即触发回调更新 TextView。但如果此时旋转屏幕,TextView 的值又变回了"张三"。

原因:我们绕过了 ViewModelProvider,没有用到它背后的存储机制。旋转屏幕后 Activity 重建,new MyViewModel() 重新执行,构造函数里又把 _userName 设为了 "张三"。ViewModel 类本身只是一个数据容器,不具备跨配置变更存活的魔法。
2ViewModel 到底是什么

严格来说,ViewModel 这个类本身:

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

真正保持状态的类是 ViewModelStore。正如 Android 开发者文档 About ViewModel 所述:

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

—— Android 开发者文档 · ViewModel

二、ViewModelStoreOwner:跨配置存活的入口

1ViewModelStoreOwner 接口

ViewModelStoreOwner 接口只定义了一个属性:

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

我们使用 ViewModelProvider 时传入的 Activity 就是 ViewModelStoreOwner。Activity 的基类 ComponentActivity 实现了该接口:

MyViewModel myVm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // this = Activity = ViewModelStoreOwner
    myVm = new ViewModelProvider(this).get(MyViewModel.class);
}
注意:如果在 onCreate 之前调用 new ViewModelProvider(this).get(...),ComponentActivity 会抛出异常:"Your activity is not yet attached to the Application instance."。很多 Android 初学者都见过这个错误,看源码后就能释怀了。
2ComponentActivity 中的核心实现

ComponentActivity 对 viewModelStore 的 getter 实现如下:

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 旋转屏幕状态不丢失的核心实现。

1
首次启动
nc == null → 创建空的 ViewModelStore
2
ViewModel 被创建
ViewModelProvider.get() 创建并存入 Store
3
屏幕旋转
Activity 销毁 → ViewModelStore 保存在 NonConfigurationInstances
4
Activity 重建
nc != null → _viewModelStore 从 nc 恢复,所有 ViewModel 实例都在

三、onRetainNonConfigurationInstance:销毁前保存 ViewModelStore

1系统在销毁 Activity 前做了什么

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

@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 的该方法:

@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}
完整链路:onRetainNonConfigurationInstance() 打包 ViewModelStore → 系统保留 → Activity 重建后 getLastNonConfigurationInstance() 取回 → ensureViewModelStore() 恢复 _viewModelStore。

四、ViewModelStore 与 ViewModelProvider:HashMap 仓库 + 代理模式查找

1ViewModelStore 就是一个 HashMap

ViewModelStore 的内部实现非常简单,核心就是一个 HashMap:

public open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.clear()
    }

    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

所以所谓的"跨配置存活"本质上就是:一个 HashMap 被藏在 NonConfigurationInstances 里,Activity 重建时把它找回来。毫不神秘。

2ViewModelProvider 的代理模式

ViewModelProvider 采用了经典的代理模式

public actual open class ViewModelProvider
private constructor(private val impl: ViewModelProviderImpl)

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

@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),
)

get() 方法直接委托给 impl:

@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)
3ViewModelProviderImpl.getViewModel():先查再创

核心逻辑在 ViewModelProviderImpl 中:

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

用流程图总结:

get(modelClass)
store[key] 查找
找到了?
直接返回已有实例
get(modelClass)
store[key] 查找
未找到
createViewModel()
store.put(key, vm)
核心逻辑只有两步:第一次调用 get() 时执行 createViewModel() 并存入 ViewModelStore;当配置变更再次调用 get() 时,直接从 store[key] 返回已有实例,彻底避免数据丢失。

五、ViewModel 的生命周期:何时销毁

1官方文档的描述

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

ViewModel 的生命周期与其作用域直接关联。ViewModel 会一直保留在内存中,直到其作用域 ViewModelStoreOwner 消失。以下上下文中可能会发生这种情况:对于 activity,是在 activity 完成时;对于 Navigation 条目,是在 Navigation 条目从返回堆栈中移除时;对于可组合项,是在可组合项退出组合时。

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

2ComponentActivity 中的 ON_DESTROY 处理

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

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

关键判断:if (!isChangingConfigurations)。在当前 Activity 销毁时,它会判断是否是配置发生改变导致的销毁。如果不是(即 Activity 是正常结束且不再需要),才调用 viewModelStore.clear() 方法清理所有 ViewModel。

这就是 ViewModel 在旋转屏幕时存活的另一半机制:onRetainNonConfigurationInstance 负责"保存",ON_DESTROY 中的 isChangingConfigurations 判断负责"不清理"。保存 + 不清理 = 跨配置存活。
3ViewModel.clear() 与 ViewModelImpl.clear()

ViewModelStore.clear() 会遍历 HashMap 并依次调用所有 ViewModel 的 clear() 方法。我们来看 ViewModel 的 clear() 方法:

@MainThread
internal actual fun clear() {
    impl?.clear()
    onCleared()
}

ViewModel 先把一些资源的回收交给代理类 ViewModelImpl 去实现,然后再执行我们重写的 onCleared() 进行自定义资源的回收:

@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数据容器,解耦 UI 数据自身无存活能力
ViewModelStoreHashMap 仓库,持有 ViewModel跨配置存活的容器
ViewModelStoreOwner对外暴露 viewModelStore接口,ComponentActivity 实现
NonConfigurationInstancesActivity 重建时保留对象onRetainNonConfigurationInstance 保存,getLastNonConfigurationInstance 恢复
ViewModelProvider门面,代理给 ViewModelProviderImpl代理模式
ViewModelProviderImpl先查后创 + 线程安全synchronized + store 读写
ON_DESTROY 观察者判断是否因配置变更销毁isChangingConfigurations → 不清理 Store
ViewModelImpl.clear()关闭 addCloseable 资源viewModelScope、自定义 AutoCloseable

六、核心思想总结

核心要点

一句话总结

ViewModel 只是包装盒,ViewModelStore 才是保管箱——后者依靠 onRetainNonConfigurationInstance 在 Activity 销毁前保存、ON_DESTROY 的 isChangingConfigurations 判断避免清理、getLastNonConfigurationInstance 在新 Activity 中恢复,完成了一个完整的跨配置存活闭环。