ViewModel
问题
ViewModel 是什么?为什么它能在配置变更时保留数据?
答案
核心概念
ViewModel 用于以生命周期感知的方式存储和管理 UI 相关数据。它在配置变更(如屏幕旋转)时不会被销毁,直到其关联的 Activity 完成(finish)或 Fragment 被分离。
基本使用
class UserViewModel(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle // 进程死亡后恢复数据
) : ViewModel() {
// UI 状态
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
// SavedStateHandle 持久化搜索关键词
val searchQuery = savedStateHandle.getStateFlow("query", "")
fun loadUser(userId: Long) {
viewModelScope.launch {
_uiState.value = UserUiState(loading = true)
val user = repository.getUser(userId)
_uiState.value = UserUiState(user = user)
}
}
fun updateSearchQuery(query: String) {
savedStateHandle["query"] = query
}
}
// Activity / Fragment 中获取
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { renderUi(it) }
}
}
}
}
ViewModel 存活原理
| 场景 | Activity | ViewModel |
|---|---|---|
| 屏幕旋转 | 销毁 → 重建 | 存活 |
| 返回键退出 | 销毁 | onCleared() |
| 进程被杀 | 销毁 | 销毁(需 SavedStateHandle) |
| Navigation 导航离开 | 销毁 | onCleared() |
ViewModel 不应持有 View 或 Context 引用
ViewModel 的生命周期长于 Activity,持有 Activity 引用会导致内存泄漏。如需 Context,使用 AndroidViewModel 持有 Application Context。
viewModelScope
viewModelScope 是 ViewModel 的内置协程作用域,在 onCleared() 时自动取消:
class MyViewModel : ViewModel() {
init {
viewModelScope.launch {
// ViewModel 被清除时自动取消
}
}
}
常见面试问题
Q1: ViewModel 为什么能在旋转屏幕后存活?
答案:
ViewModelStoreOwner(Activity)持有一个 ViewModelStore(本质是 HashMap)。旋转时 Activity 销毁,但 ActivityThread 通过 NonConfigurationInstances 机制将 ViewModelStore 保存下来,新的 Activity 实例从中取回同一个 ViewModel。
Q2: ViewModel 和 SavedStateHandle 的区别?
答案:
| 特性 | ViewModel | SavedStateHandle |
|---|---|---|
| 存活于配置变更 | ✅ | ✅ |
| 存活于进程死亡 | ❌ | ✅ |
| 数据大小限制 | 无(内存中) | ~1MB(Bundle 限制) |
| 存储类型 | 任意对象 | 可序列化的简单数据 |
UI 状态(如滚动位置、输入框文字)应存入 SavedStateHandle,业务数据从 Repository 重新加载即可。
Q3: Fragment 之间如何共享 ViewModel?
答案:
使用 Activity 作为 ViewModelStoreOwner,多个 Fragment 获取同一个 ViewModel 实例:
// Fragment A 和 Fragment B 中
val sharedViewModel: SharedViewModel by activityViewModels()
也可以用 Navigation Graph 作为 scope:
val sharedViewModel: SharedViewModel by navGraphViewModels(R.id.my_nav_graph)