Android ViewModel 生命周期与配置变更机制深度解析
引言
在现代 Android 开发(Modern Android Development, MAD)体系中,Jetpack 组件库已成为构建稳定、可维护应用的基础设施。其中,ViewModel 作为架构组件的核心部分,解决了 Activity 和 Fragment 在配置变更(如屏幕旋转、键盘弹出)或进程销毁重建时丢失 UI 状态的问题。
许多开发者能够熟练使用 ViewModel API,但在面试或深入源码分析时,往往对底层实现机制一知半解。理解 ViewModel 如何在 Activity 销毁重建后依然保留数据,对于掌握 Android 生命周期管理至关重要。本文将深入剖析 ViewModel 的源码逻辑,揭示其横竖屏切换不销毁的根本原因。
ViewModel 核心优势与 MVVM 架构
ViewModel 是 Android Jetpack 中的重要组件,旨在以生命周期感知的方式存储和管理界面相关的数据。其核心优势在于:
- 配置变更存活:当 Activity 因屏幕旋转等配置变化而销毁重建时,ViewModel 实例不会销毁。
- 内存安全:ViewModel 持有的是弱引用相关的上下文,避免了常见的内存泄漏问题。
- 数据持久化:在进程被系统杀死前,ViewModel 中的数据通常能得以保留(配合 SavedStateHandle)。
在 MVVM 架构中,ViewModel 充当了 View(Activity/Fragment)与 Model 之间的桥梁,负责处理业务逻辑并暴露数据给 UI 层观察。
配置变更下的 Activity 生命周期
要理解 ViewModel 为何不销毁,首先需要了解 Android 系统在配置变更时的行为。当用户旋转设备时,系统会触发以下流程:
- 当前 Activity 销毁:系统调用
onPause(),onStop(),onDestroy()。 - 新 Activity 创建:系统创建新的 Activity 实例,调用
onCreate(),onStart(),onResume()。 - 数据传递:旧实例中的非配置信息会被传递给新实例。
通常情况下,Activity 销毁意味着其持有的所有对象都会被垃圾回收。但 Android 提供了一种机制,允许 Activity 在销毁前保留某些非配置实例(Non-Configuration Instances),以便在新实例中恢复。
ViewModelProvider 与 ViewModelStore 源码分析
ViewModelProvider 结构
ViewModelProvider 是获取 ViewModel 实例的入口类。它主要持有一个工厂(Factory)和一个存储(Store)。
package androidx.lifecycle;
public class ViewModelProvider {
public interface Factory {
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
}
从源码可见,ViewModelProvider 本身并不存储 ViewModel 数据,而是通过 ViewModelStore 进行托管。
get() 方法逻辑
当调用 get(Class) 方法时,ViewModelProvider 执行以下逻辑:
- 生成唯一的 Key(通常是类的全限定名)。
- 尝试从
ViewModelStore中获取已存在的 ViewModel。 - 如果存在且类型匹配,直接返回。
- 如果不存在或类型不匹配,则通过
Factory创建新实例并存入 Store。
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
} else {
// 类型不匹配时的处理
}
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
这一机制确保了同一 Key 对应的 ViewModel 在生命周期内只会被创建一次。
ViewModelStore 实现
ViewModelStore 本质上是一个 HashMap<String, ViewModel> 的封装。
package androidx.lifecycle;
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
关键点在于 clear() 方法。只有当 clear() 被调用时,ViewModel 才会真正销毁。因此,问题的核心转变为:为什么在屏幕旋转时,clear() 没有被调用?
关键机制:NonConfigurationInstances
ViewModelStoreOwner 接口
ViewModelProvider 构造时需要传入 ViewModelStoreOwner。ComponentActivity(即 AppCompatActivity 的基类)实现了该接口。
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
FragmentActivity 的实现
FragmentActivity 重写了 getViewModelStore() 方法,这是保存 ViewModel 的关键环节。
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
这里有两个分支:
- 首次创建:如果
mViewModelStore为空,检查是否有保存的非配置实例(getLastNonConfigurationInstance)。如果有,从中恢复viewModelStore。 - 新建实例:如果没有保存的实例,则创建一个新的
ViewModelStore。
这意味着,只要能在旧实例中找到保存的 ViewModelStore,新 Activity 就能复用同一个 Store,从而复用其中的 ViewModel。
onRetainNonConfigurationInstance
系统如何保存这个 Store?答案在于 onRetainNonConfigurationInstance() 方法。
在 FragmentActivity 中,该方法被重写为:
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
注意 nci.viewModelStore = mViewModelStore; 这行代码。系统在销毁 Activity 之前调用此方法,将当前的 ViewModelStore 引用放入 NonConfigurationInstances 对象中。
attach 过程与数据传递
当系统重建 Activity 时,会通过 attach() 方法将旧的 NonConfigurationInstances 传递给新的 Activity 实例。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident, Application application,
Intent intent, ActivityInfo info, CharSequence title, Activity parent,
String id, NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback,
IBinder assistToken)
参数 lastNonConfigurationInstances 包含了上一轮 Activity 保存的状态。在 onCreate() 中,系统会调用 getLastNonConfigurationInstance() 来获取这个对象,进而恢复 ViewModelStore。
LifecycleObserver 与清理时机
既然有了保存机制,那么何时清理呢?ComponentActivity 内部注册了一个 LifecycleEventObserver。
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mContextAwareHelper.clearAvailableContext();
// 关键判断:是否正在改变配置
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
这里有一个至关重要的判断:if (!isChangingConfigurations())。
- 屏幕旋转:
isChangingConfigurations()返回true。此时不调用clear(),ViewModelStore保留,等待下一轮onCreate恢复。 - 正常销毁:例如用户按下 Home 键导致进程被杀,或者调用
finish()。此时isChangingConfigurations()返回false。系统会调用clear(),触发onCleared(),ViewModel 正式销毁。
这一设计完美区分了'临时重建'和'永久销毁'两种场景。
扩展:SavedStateHandle 与现代实践
虽然 ViewModel 在配置变更时存活,但如果进程被系统杀死(Process Death),ViewModel 中的数据也会丢失。为了解决这个问题,AndroidX 引入了 SavedStateHandle。
class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
init {
_data.value = savedStateHandle["key"] ?: "Default"
}
fun update(value: String) {
savedStateHandle["key"] = value
_data.value = value
}
}
SavedStateHandle 会将数据序列化保存到磁盘(Bundle),即使进程死亡也能恢复。这与 ViewModel 的内存存储形成了互补。
常见误区与最佳实践
1. 避免在 ViewModel 中持有 Context
由于 ViewModel 生命周期长于 Activity,如果在 ViewModel 中强引用 Activity 的 Context,会导致严重的内存泄漏。应使用 ApplicationContext 或通过 ViewModelProvider.Factory 注入所需资源。
2. Fragment 与 ViewModel 共享
在 Fragment 中使用 ViewModel 时,可以通过 activityViewModels() 委托属性让 Fragment 共享 Activity 级别的 ViewModel。这样无论 Fragment 如何切换,ViewModel 都保持不变。
class MyFragment : Fragment() {
private val viewModel: MyViewModel by activityViewModels()
}
3. 测试考虑
在单元测试中,可以直接实例化 ViewModel,无需模拟复杂的 Activity 生命周期。这简化了业务逻辑的验证过程。
总结
ViewModel 之所以在横竖屏切换时不销毁,依赖于 Android 系统的 NonConfigurationInstances 机制以及 ComponentActivity 内部的 LifecycleObserver 判断。
- 保存阶段:
onRetainNonConfigurationInstance()将ViewModelStore存入NonConfigurationInstances。 - 恢复阶段:新 Activity 的
onCreate()通过getLastNonConfigurationInstance()恢复 Store。 - 清理阶段:仅在非配置变更的销毁事件中调用
clear()。
理解这一机制,不仅有助于应对技术面试,更能帮助开发者在复杂场景下合理管理应用状态,构建更健壮的应用程序。随着 Jetpack 的发展,结合 SavedStateHandle 和 Hilt 依赖注入,可以进一步简化状态管理的复杂度,提升开发效率。
在实际项目中,建议始终遵循官方文档推荐的架构模式,利用 ViewModel 处理 UI 相关数据,确保应用在各种生命周期事件下的稳定性与一致性。


