跳到主要内容

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 存活原理

场景ActivityViewModel
屏幕旋转销毁 → 重建存活
返回键退出销毁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 的区别?

答案

特性ViewModelSavedStateHandle
存活于配置变更
存活于进程死亡
数据大小限制无(内存中)~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)

相关链接