ViewModel 源码解析
🔄 ViewModel 源码解析
ViewModel 为什么旋转屏幕不销毁?onCleared 到底什么时候调用?SavedStateHandle 如何做到进程死亡恢复?本文从源码彻底搞懂 ViewModel 机制。
一、ViewModel 解决了什么
flowchart LR
A[旋转屏幕<br>Activity 重建] --> B{数据在 Activity 中?}
B -->|是| C[数据丢失<br>需手动保存]
B -->|否| D[ViewModel<br>配置变化保留]
D --> E[onCleared 兜底释放]核心问题:旋转屏幕时 Activity 销毁重建,UI 数据(列表加载一半、表单输入)会丢失。
ViewModel 方案:数据移出 UI,存到不随配置变化销毁的 ViewModelStore 中。
二、源码核心:ViewModelStore 与 ViewModelProvider
2.1 关键类关系
flowchart TD
A[ViewModelProvider<br>入口] --> B[Factory<br>创建实例]
A --> C[ViewModelStore<br>持有实例 Map]
C --> D[ViewModel 实例<br>Map<String, ViewModel>]
B --> E[AndroidViewModelFactory<br>默认工厂]// 1. ViewModelStore:简单的 HashMap 容器
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();
}
}// 2. ViewModelProvider.get 核心逻辑(简化)
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel; // 已有实例直接复用
}
ViewModel newViewModel = mFactory.create(modelClass); // 工厂创建
mViewModelStore.put(key, newViewModel);
return (T) newViewModel;
}2.2 为什么旋转不销毁
// ComponentActivity / Activity
// ViewModelStore 由 NonConfigurationInstances 持有
// —— Activity 重建时通过 retainNonConfigurationInstance 传递!
public final Object onRetainNonConfigurationInstance() {
// 保存 ViewModelStore 等状态
return new NonConfigurationInstances(mViewModelStore, ...);
}
protected void onCreate(Bundle savedInstanceState) {
// 重建时从上一实例恢复 ViewModelStore
NonConfigurationInstances nci = getLastNonConfigurationInstance();
if (nci != null) mViewModelStore = nci.viewModelStore;
}sequenceDiagram
participant A1 as Activity(旧)
participant S as 系统
participant A2 as Activity(新)
A1->>S: onRetainNonConfigurationInstance<br>传递 ViewModelStore
S->>A2: getLastNonConfigurationInstance<br>恢复 ViewModelStore
A2->>A2: 同一个 ViewModel 实例继续使用💡 关键:ViewModelStore 不挂在 Activity 实例上,而是通过
NonConfigurationInstances在重建时原样传递,所以 ViewModel 不销毁。只有finish()(用户主动关闭)才会真正 clear。
三、onCleared 调用时机
// ComponentActivity 销毁逻辑
public void onDestroy() {
super.onDestroy();
if (!isChangingConfigurations()) { // 关键判断!
getViewModelStore().clear(); // 非配置变化销毁 → 清理 ViewModel
}
}| 场景 | isChangingConfigurations | onCleared |
|---|---|---|
| 旋转屏幕/切换深色模式 | true | ❌ 不调用 |
| 用户按返回键 finish() | false | ✅ 调用 |
| 任务被移除 | false | ✅ 调用 |
| 进程被杀(无 onDestroy) | — | ❌ 不调用(进程直接没了) |
// onCleared 常见用途
class TimerViewModel : ViewModel() {
private val job = SupervisorJob()
init {
// 启动一个后台任务
viewModelScope.launch { /* ... */ }
}
override fun onCleared() {
super.onCleared()
job.cancel() // 取消协程
player.release() // 释放播放器
// 移除监听器、关闭流等
}
}⚠️ 坑:onCleared 不保证在进程被杀时调用(进程死亡无回调),所以关键数据要持久化到 SavedStateHandle/Room/DataStore。
四、Factory 与默认工厂
// ViewModelProvider 构造
public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
this(owner.getViewModelStore(), factory);
}
// 默认工厂:根据构造参数类型自动提供
public static class DefaultFactory extends SavedStateViewModelFactory {
// 支持 @Inject 构造 / SavedStateHandle / Application 参数
}// 自定义 Factory:带参 ViewModel
class DetailViewModel(
private val userId: Long,
private val repository: UserRepository
) : ViewModel()
class DetailViewModelFactory(
private val userId: Long,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DetailViewModel(userId, repository) as T
}
}
// 使用
viewModels { DetailViewModelFactory(userId, repo) }五、viewModelScope 原理
// 源码:ViewModel 扩展属性
public val ViewModel.viewModelScope: CoroutineScope
get() {
val scope = this.getTag(JOB_KEY) as? CloseableCoroutineScope
if (scope != null) return scope
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(
SupervisorJob() + Dispatchers.Main.immediate))
}// 关键:ViewModel 销毁时关闭 scope
public void clear() {
// ...
Closeable closeable = mBagOfTags.remove(JOB_KEY);
if (closeable != null) closeable.close(); // 取消 viewModelScope
}💡
viewModelScope使用SupervisorJob + Main.immediate。ViewModel 清除时自动取消,无需手动管理协程生命周期。注意:网络请求放 viewModelScope 会在离开页面时自动取消(合理),但轮询/下载等需特殊处理。
六、SavedStateHandle 原理
// SavedStateHandle:进程死亡恢复的核心
public final class SavedStateHandle {
private final Map<String, Object> mRegular; // 普通值
private final SavedStateRegistry.SavedStateProvider mSavedStateProvider;
// getLiveData:把值包装成 LiveData
public <T> MutableLiveData<T> getLiveData(String key) {
// 存在则复用,否则创建并注册 SavedStateProvider
}
// 进程恢复流程
// onSaveInstanceState → 保存 mRegular → 重建时恢复
}class MainViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 初始化读取(进程死亡恢复)
val count = savedStateHandle.getLiveData<Int>("count")
fun addCount() {
count.value = (count.value ?: 0) + 1
}
}sequenceDiagram
participant V as ViewModel
participant S as SavedStateHandle
participant B as Bundle(系统)
V->>S: 写入 key/value
S->>B: onSaveInstanceState 时序列化
Note over B: 进程被杀...
B->>S: 重建时恢复 Bundle
S->>V: getLiveData 返回恢复值七、高频面试题
Q1:ViewModel 为什么旋转屏幕不销毁?
查看答案
ViewModel 实例存储在 ViewModelStore 中,而 ViewModelStore 通过 Activity 的 onRetainNonConfigurationInstance 在配置变化时传递给新 Activity 实例(getLastNonConfigurationInstance 恢复),因此 ViewModel 不随 Activity 重建销毁。ViewModelStore.clear() 只在 !isChangingConfigurations() 时(即用户主动 finish)调用,配置变化时不会 clear。
Q2:onCleared 什么时候调用?进程被杀会调用吗?
查看答案
onCleared 在 ViewModelStore.clear() 时调用,即 Activity/Fragment 真正销毁且不是配置变化时:用户按返回键、finish() 被调用、任务移除。旋转屏幕等配置变化不调用。进程被杀(系统回收)不会回调 onCleared(进程直接终止),所以不能在 onCleared 里依赖持久化,关键数据要用 SavedStateHandle/Room 保存。onCleared 典型用途:取消协程、释放播放器、移除监听器。
Q3:viewModelScope 是怎么自动取消的?
查看答案
viewModelScope 是 ViewModel 的扩展属性,内部创建 CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)并作为 tag 存入 ViewModel。ViewModel.clear() 时会关闭该 Closeable,从而取消整个 scope 的协程。因此放 viewModelScope 的协程在 ViewModel 销毁时自动取消,无需手动 cancel。注意 SupervisorJob 使一个子协程失败不影响兄弟协程。
Q4:ViewModel 中可以持有 Activity/View 引用吗?为什么?
查看答案
不能。ViewModel 生命周期比 Activity/View 长(配置变化时 ViewModel 存活而 View 重建),持有 View 引用会导致内存泄漏——View 无法被 GC。正确做法:ViewModel 只暴露 StateFlow/LiveData,View 通过 observe/collect 订阅;需要 Context 时用 AndroidViewModel(Application 级别)或用 SavedStateHandle;需要一次性事件用 Channel 等。
Q5:SavedStateHandle 如何实现进程死亡恢复?和 onSaveInstanceState 什么关系?
查看答案
SavedStateHandle 内部维护一个普通 Map(mRegular)并注册为 SavedStateRegistry 的 SavedStateProvider。Activity 执行 onSaveInstanceState 时,系统收集所有 provider 的 Bundle,SavedStateHandle 把自己的值写入;进程被杀后重建时,系统把恢复的 Bundle 交回 SavedStateHandle,getLiveData/get 就能读到之前的值。本质是"把 ViewModel 数据纳入系统级状态保存机制",相比手动 onSaveInstanceState 更集中、类型安全。
小结
- ViewModelStore(HashMap)+ 工厂创建 + NonConfigurationInstances 传递 = 配置变化不销毁
- 只有非配置变化的销毁(finish)才触发 onCleared
- 进程被杀无回调,关键数据必须持久化
- 自定义 Factory 支持带参构造;Hilt @HiltViewModel 自动生成
- viewModelScope = SupervisorJob + Main,clear 时自动取消
- SavedStateHandle 通过 SavedStateRegistry 桥接系统状态保存
📖 进阶阅读:ViewModel + LiveData | SavedStateHandle 状态保存 | Lifecycle 组件详解