一、先说结论:ViewModel 不能感知页面生命周期
很多人以为:
val viewModel = ViewModelProvider(this)[MainViewModel::class.java]
这里传了 this,而 this 是 Activity,所以 ViewModel 应该就能知道 Activity 的生命周期。
这个理解是错的。
ViewModelProvider(this) 里的 this,并不是把 Activity 传给 ViewModel,也不是让 ViewModel 持有 Activity。
这里的 this 本质上表示:
这个 ViewModel 要存放在哪个 ViewModelStore 里面。
也就是说,Activity 只是作为一个“宿主”,给 ViewModel 提供一个存放位置。
可以理解为:
- Activity 是一个房间;
- ViewModel 是一个箱子;
- ViewModelStore 是房间里的柜子;
ViewModelProvider(this)的意思是:把这个箱子放到当前房间的柜子里。
但是:
箱子被放进房间,不代表箱子知道房间什么时候开门、关门、亮灯、熄灯。
所以,ViewModel 不知道 Activity 什么时候执行了:
onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
ViewModel 只知道一件事:
自己什么时候要被销毁。
也就是:
override fun onCleared() {
super.onCleared()
}
二、ViewModel 到底有没有生命周期?
这个问题面试里很容易被问。
要回答得严谨一点:
ViewModel 有自己的存活周期,但它不是 LifecycleOwner,不能感知页面生命周期事件。
也就是说,ViewModel 不是完全没有生命周期。
它确实有一个从创建到销毁的过程:
class MainViewModel : ViewModel() {
init {
// ViewModel 创建
}
override fun onCleared() {
super.onCleared()
// ViewModel 销毁
}
}
但是它没有类似 Activity 的这些回调:
onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
ViewModel 只有:
onCleared()
所以更准确的说法是:
ViewModel 有自己的生命周期边界,但没有页面生命周期感知能力。
三、ViewModel 什么时候会销毁?
这是理解 ViewModel 的核心。
ViewModel 的销毁时机和 Activity 的销毁时机并不完全一样。
1. 配置变更时,ViewModel 不会销毁
比如:
- 屏幕旋转;
- 深色模式切换;
- 语言切换;
- 字体大小变化;
- 某些配置变化导致 Activity 重建。
这些情况下,Activity 会经历销毁和重建。
旧 Activity 会执行:
onDestroy()
然后新的 Activity 会重新执行:
onCreate()
但是 ViewModel 不会销毁。
它会被保留下来,然后交给新的 Activity 继续使用。
这就是 ViewModel 最核心的价值之一:
在配置变更时保存数据,避免 Activity 重建导致数据丢失。
2. Activity 真正结束时,ViewModel 才会销毁
比如:
- 用户按返回键退出页面;
- 调用了
finish(); - Fragment 从返回栈中被彻底移除;
- 宿主真正结束。
这时候 ViewModel 才会执行:
onCleared()
所以可以总结为:
| 场景 | Activity 是否重建 | ViewModel 是否销毁 |
|---|---|---|
| 屏幕旋转 | 是 | 否 |
| 深色模式切换 | 是 | 否 |
| 语言切换 | 是 | 否 |
| 按返回键退出页面 | 是/否不重要 | 是 |
| 调用 finish() | 是 | 是 |
| Fragment 被彻底移除 | 是 | 是 |
四、为什么 ViewModel 不能持有 Activity 或 Fragment?
这是 ViewModel 设计里非常重要的一点。
ViewModel 的目的主要有两个:
- 在配置变更时保留数据;
- 让业务逻辑和 UI 解耦,避免内存泄漏。
如果 ViewModel 持有 Activity,会发生什么?
假设你这么写:
class MainViewModel(
private val activity: Activity
) : ViewModel()
然后手机旋转屏幕。
此时流程是:
- 旧 Activity 被销毁;
- ViewModel 因为配置变更不会销毁;
- ViewModel 还持有旧 Activity;
- 旧 Activity 无法被 GC 回收;
- 内存泄漏。
这就是为什么官方不建议在 ViewModel 里持有:
- Activity;
- Fragment;
- View;
- Context,尤其是 Activity Context;
- LifecycleOwner;
- ViewBinding;
- Adapter;
- Dialog;
- RecyclerView;
- NavController。
这些对象都和 UI 生命周期强相关。
ViewModel 里应该放的是:
- 页面状态;
- 业务逻辑;
- 网络请求;
- 数据转换;
- Repository 调用;
- LiveData / StateFlow / SharedFlow;
- SavedStateHandle;
- Application 级别 Context,必要时可通过 AndroidViewModel 使用。
五、viewModelScope 能不能感知页面前后台?
不能。
很多人会误以为:
viewModelScope.launch {
// do something
}
既然 viewModelScope 会自动取消,那它是不是能感知 Activity 的生命周期?
不是。
viewModelScope 只和 ViewModel 自己的销毁绑定。
也就是说:
viewModelScope.launch {
while (true) {
delay(1000)
println("running")
}
}
如果页面只是进入后台:
onPause()
onStop()
这个协程不会自动停止。
如果屏幕旋转,Activity 重建,ViewModel 仍然存在,那么这个协程也不会停止。
只有当 ViewModel 执行:
onCleared()
时,viewModelScope 里的协程才会自动取消。
所以结论是:
viewModelScope 感知的是 ViewModel 的销毁,不是 Activity / Fragment 的 onPause、onStop、onDestroyView。
六、页面 onResume / onPause 相关逻辑应该写在哪里?
如果业务需要在页面可见时刷新数据,或者页面不可见时停止某些任务,应该由 UI 层监听生命周期,再通知 ViewModel。
也就是说:
生命周期是 Activity / Fragment 的事情,业务处理可以交给 ViewModel。
比如:
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainViewModel>()
override fun onResume() {
super.onResume()
viewModel.onPageResume()
}
override fun onPause() {
super.onPause()
viewModel.onPagePause()
}
}
ViewModel 中只处理业务:
class MainViewModel : ViewModel() {
fun onPageResume() {
// 页面回到前台
// 可以刷新数据、恢复轮询、上报埋点
}
fun onPagePause() {
// 页面进入后台
// 可以暂停任务、停止轮询、保存状态
}
}
这种写法是合理的。
注意,这并不是 ViewModel 主动感知生命周期,而是:
Activity 感知生命周期,然后主动调用 ViewModel 的方法。
七、也可以用 LifecycleObserver 解耦生命周期监听
如果不想直接重写 onResume()、onPause(),也可以这样写:
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
viewModel.onPageResume()
}
override fun onPause(owner: LifecycleOwner) {
viewModel.onPagePause()
}
})
这和重写生命周期方法本质是一样的。
区别只是写法不同。
核心仍然是:
UI 层监听生命周期,ViewModel 被动接收通知。
不要反过来让 ViewModel 去监听 Activity。
八、observe(this) 里的 this 又是什么意思?
很多人会被这句代码搞混:
viewModel.count.observe(this) { count ->
textView.text = count.toString()
}
这里也传了 this。
于是有人就会问:
既然 observe(this) 可以传 Activity,为什么又说 ViewModel 不能感知生命周期?
关键点在于:
感知生命周期的是 LiveData,不是 ViewModel。
这句代码里:
viewModel.count.observe(this) { }
真正拿到 this 的是 LiveData。
LiveData 会根据 Activity 的生命周期来决定:
- 页面活跃时,分发数据;
- 页面不活跃时,暂缓分发;
- 页面销毁时,自动移除观察者。
但是 ViewModel 本身并没有拿到 Activity。
ViewModel 只是持有了一个 LiveData:
class MainViewModel : ViewModel() {
private val _count = MutableLiveData<Int>()
val count: LiveData<Int> = _count
}
Activity 订阅这个 LiveData:
viewModel.count.observe(this) { count ->
// 更新 UI
}
所以流程是:
- ViewModel 更新 LiveData;
- LiveData 通知观察者;
- LiveData 根据 LifecycleOwner 判断当前页面能不能收到数据;
- Activity 收到数据后更新 UI。
ViewModel 并不知道 Activity 当前是 resumed、paused 还是 destroyed。
九、Fragment 里 observe 不要随便传 this
这里还要补充一个非常高频的坑。
在 Fragment 里,很多人会这样写:
viewModel.data.observe(this) {
// update UI
}
这通常不推荐。
因为 Fragment 有两个生命周期:
- Fragment 自己的生命周期;
- Fragment 的 View 生命周期。
Fragment 的 View 可能已经销毁了,但 Fragment 还没销毁。
比如:
onDestroyView()
执行之后,Fragment 还可能存在。
如果你用 this 作为 LifecycleOwner,观察者可能还活着,但 ViewBinding 已经没了,这就容易导致:
- 空指针;
- 重复观察;
- 内存泄漏;
- UI 更新异常。
所以在 Fragment 里应该使用:
viewModel.data.observe(viewLifecycleOwner) {
// update UI
}
原因是:
UI 更新应该跟 Fragment 的 View 生命周期绑定,而不是跟 Fragment 本身绑定。
这是面试和项目里都非常重要的细节。
十、LiveData 和 Flow 的生命周期区别
如果项目里用的是 LiveData:
viewModel.data.observe(viewLifecycleOwner) {
// update UI
}
LiveData 自带生命周期感知能力。
如果项目里用的是 Flow / StateFlow:
lifecycleScope.launch {
viewModel.dataFlow.collect {
// update UI
}
}
这样写还不够严谨。
因为普通 collect 不会自动根据页面 STARTED / STOPPED 停止和恢复收集。
更推荐这样:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.dataFlow.collect { data ->
// update UI
}
}
}
在 Fragment 里一般写成:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.dataFlow.collect { data ->
// update UI
}
}
}
这样可以做到:
- 页面进入 STARTED 状态后开始收集;
- 页面 STOPPED 后停止收集;
- 页面再次 STARTED 后重新收集;
- View 销毁后自动取消。
这才是 Flow 配合页面生命周期的推荐方式。
十一、完整 MVVM 流程应该这样理解
MVVM 里不是 ViewModel 主动操作 View,而是双向通过方法和数据流进行沟通。
1. UI 层通知 ViewModel
比如用户点击按钮:
button.setOnClickListener {
viewModel.onLoginClick()
}
比如页面回到前台:
override fun onResume() {
super.onResume()
viewModel.onPageResume()
}
也就是说:
Activity / Fragment 感知事件
↓
调用 ViewModel 方法
↓
ViewModel 执行业务逻辑
2. ViewModel 暴露状态给 UI
ViewModel 不直接操作 TextView、RecyclerView、Toast、Dialog。
它应该暴露状态:
class MainViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState>()
val uiState: LiveData<UiState> = _uiState
fun loadData() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val data = repository.getData()
_uiState.value = UiState.Success(data)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "未知错误")
}
}
}
}
Activity / Fragment 观察状态:
viewModel.uiState.observe(this) { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
}
整体流程是:
ViewModel 更新状态
↓
LiveData / StateFlow 发出数据
↓
Activity / Fragment 收到数据
↓
更新 UI
可以把这两个点总结成一句话:
页面生命周期事件是 UI 主动告诉 ViewModel;数据变化是 ViewModel 通过 LiveData/StateFlow 通知 UI。
这两个方向完全不同,很多人混淆就是因为都出现了this、viewModel、observe这些代码。
下面帮你按面试和项目最容易混的点梳理清楚。
一、先建立总模型:MVVM 里有两个方向
方向 1:UI → ViewModel
例如:
override fun onResume() {
super.onResume()
viewModel.onPageResume()
}
或者:
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
viewModel.onPageResume()
}
})
这个方向表达的是:
Activity / Fragment 感知生命周期
↓
主动调用 ViewModel 方法
↓
ViewModel 执行业务逻辑
比如:
- 页面回到前台,刷新数据;
- 页面进入前台,开启轮询;
- 页面进入后台,暂停任务;
- 用户点击按钮,调用登录逻辑;
- 用户下拉刷新,调用加载逻辑。
所以这个方向叫:
UI 通知 ViewModel 做事。
方向 2:ViewModel → UI
例如:
viewModel.count.observe(this) { count ->
tvCount.text = count.toString()
}
这个方向表达的是:
ViewModel 更新数据
↓
LiveData 通知观察者
↓
Activity / Fragment 收到回调
↓
更新 UI
比如:
- ViewModel 请求接口成功;
- ViewModel 更新 LiveData;
- Activity 收到数据;
- Activity 渲染页面。
所以这个方向叫:
ViewModel 通过数据通知 UI 更新。
二、最容易混淆的点 1:viewModel.onPageResume() 不是 ViewModel 感知生命周期
很多人看到这段代码:
override fun onResume() {
super.onResume()
viewModel.onPageResume()
}
就会误以为:
ViewModel 感知到了 Activity 的 onResume。
这是错的。
真正感知生命周期的是 Activity。
ViewModel 只是被 Activity 调了一个普通方法。
它和下面这个按钮点击本质一样:
button.setOnClickListener {
viewModel.onLoginClick()
}
onPageResume() 这个名字虽然带了 Resume,但它不是生命周期回调。
它只是你自己定义的一个普通方法:
fun onPageResume() {
loadData()
}
ViewModel 并不知道:
- Activity 是否真的执行了 onResume;
- 当前页面是否可见;
- Activity 是不是在前台;
- Fragment 的 View 是否还存在。
它只知道:
外部调用了我的
onPageResume()方法,我开始执行业务。
所以准确说法是:
不是 ViewModel 监听了生命周期,而是 UI 层监听生命周期后,主动通知 ViewModel。
三、最容易混淆的点 2:observe(this) 不是 ViewModel 感知生命周期
很多人看到:
viewModel.count.observe(this) { count ->
tvCount.text = count.toString()
}
就会以为:
ViewModel 拿到了 Activity,所以 ViewModel 可以感知 Activity 生命周期。
这也是错的。
这里的 this 不是传给 ViewModel 的,而是传给 LiveData 的。
完整理解应该是:
viewModel.count.observe(this) { }
等价于:
从 viewModel 里拿到 count 这个 LiveData
↓
调用 LiveData 的 observe 方法
↓
把 Activity 作为 LifecycleOwner 传给 LiveData
↓
LiveData 根据 Activity 生命周期决定什么时候分发数据
也就是说:
感知生命周期的是 LiveData,不是 ViewModel。
ViewModel 只是持有了一个 LiveData 对象:
class MainViewModel : ViewModel() {
private val _count = MutableLiveData<Int>()
val count: LiveData<Int> = _count
}
Activity 观察它:
viewModel.count.observe(this) { count ->
tvCount.text = count.toString()
}
ViewModel 不知道谁在观察,也不知道 Activity 当前是 onResume、onPause 还是 onDestroy。
四、最容易混淆的点 3:两个 this 不是同一个含义
在 ViewModel 相关代码里,常见两个 this:
1. ViewModelProvider(this)
val viewModel = ViewModelProvider(this)[MainViewModel::class.java]
或者:
private val viewModel: MainViewModel by viewModels()
这里的 this 表示:
这个 ViewModel 归哪个 Activity / Fragment 的 ViewModelStore 管理。
它的作用是找到 ViewModel 的存放仓库。
它不是把 Activity 传进 ViewModel。
2. observe(this)
viewModel.count.observe(this) { }
这里的 this 表示:
LiveData 根据这个 LifecycleOwner 的生命周期来分发和解绑数据。
它是给 LiveData 用的。
它也不是给 ViewModel 用的。
所以这两个 this 要分清:
| 代码 | this 给谁用 | 作用 |
|---|---|---|
ViewModelProvider(this) |
ViewModelProvider / ViewModelStore | 决定 ViewModel 归谁管理 |
observe(this) |
LiveData | 根据生命周期分发和解绑数据 |
一句话总结:
ViewModelProvider(this)的 this 是用来找仓库;observe(this)的 this 是用来控制观察者生命周期。它们都不是让 ViewModel 持有 Activity。
五、最容易混淆的点 4:UI → ViewModel 和 ViewModel → UI 不要反过来理解
正确方向是:
生命周期、点击、输入、刷新等事件:
UI → ViewModel
比如:
override fun onResume() {
super.onResume()
viewModel.onPageResume()
}
数据、状态、结果:
ViewModel → UI
比如:
viewModel.uiState.observe(this) { state ->
render(state)
}
错误理解是:
ViewModel 主动监听 Activity 生命周期
ViewModel 主动调用 Activity 更新 UI
这种写法不推荐:
class MainViewModel(
private val activity: Activity
) : ViewModel() {
fun updateUI() {
activity.findViewById<TextView>(R.id.tvName).text = "hello"
}
}
这样会导致:
- ViewModel 持有 Activity;
- 配置变更时旧 Activity 无法释放;
- 容易内存泄漏;
- ViewModel 和 UI 强耦合;
- 单元测试困难。
正确做法是:
class MainViewModel : ViewModel() {
private val _uiState = MutableLiveData<String>()
val uiState: LiveData<String> = _uiState
fun loadData() {
_uiState.value = "hello"
}
}
Activity 观察:
viewModel.uiState.observe(this) { text ->
tvName.text = text
}
六、最容易混淆的点 5:onPageResume() 里面能不能直接更新 UI?
不建议。
比如这样写就不合适:
class MainViewModel : ViewModel() {
fun onPageResume() {
textView.text = "刷新成功"
}
}
ViewModel 不应该知道 TextView。
正确写法是:
class MainViewModel : ViewModel() {
private val _uiState = MutableLiveData<String>()
val uiState: LiveData<String> = _uiState
fun onPageResume() {
loadData()
}
private fun loadData() {
_uiState.value = "刷新成功"
}
}
Activity:
viewModel.uiState.observe(this) { text ->
tvContent.text = text
}
也就是说:
UI 通知 VM:页面 resume 了
↓
VM 执行业务:刷新数据
↓
VM 更新 LiveData
↓
UI 观察数据:更新页面
ViewModel 可以决定“页面应该显示什么状态”,但不应该直接操作“具体哪个 View 怎么显示”。
七、最容易混淆的点 6:viewModelScope 不是页面生命周期作用域
很多人会把这三者混到一起:
lifecycleScope
viewModelScope
LiveData.observe(this)
它们的生命周期绑定对象不同。
1. viewModelScope
viewModelScope.launch {
loadData()
}
它绑定的是 ViewModel。
ViewModel 被销毁时,协程取消。
也就是说,只有:
onCleared()
执行时,它才会自动取消。
它不会因为 Activity 执行:
onPause()
onStop()
就自动停止。
2. lifecycleScope
lifecycleScope.launch {
// do something
}
它绑定的是 Activity / Fragment 的 Lifecycle。
但是注意,普通 lifecycleScope.launch 也不是在 onStop 自动取消,它通常会在 Lifecycle Destroyed 时取消。
如果想要 STARTED 开始、STOPPED 停止,应该用:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// collect flow
}
}
3. LiveData.observe(this)
viewModel.data.observe(this) { }
LiveData 会根据 LifecycleOwner 的状态分发数据。
页面销毁时会自动移除观察者。
总结表:
| 写法 | 绑定对象 | 主要作用 |
|---|---|---|
viewModelScope |
ViewModel | ViewModel 销毁时取消协程 |
lifecycleScope |
Activity/Fragment | 生命周期销毁时取消协程 |
observe(this) |
LifecycleOwner | LiveData 按生命周期分发数据 |
八、最容易混淆的点 7:Fragment 里不要随便用 observe(this)
Activity 中可以这样:
viewModel.data.observe(this) { data ->
render(data)
}
但是 Fragment 中更推荐:
viewModel.data.observe(viewLifecycleOwner) { data ->
render(data)
}
不要随便写:
viewModel.data.observe(this) { data ->
render(data)
}
原因是 Fragment 有两个生命周期:
- Fragment 自身生命周期;
- Fragment 的 View 生命周期。
Fragment 的 View 可能已经销毁了,但是 Fragment 自己还没销毁。
如果你用 this,观察者可能还在,但 ViewBinding 已经置空了,容易出现:
- 空指针;
- 重复观察;
- UI 更新异常;
- 内存泄漏。
所以 Fragment 中观察 LiveData,一般写:
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.tvContent.text = data
}
九、完整代码串起来理解
ViewModel
class MainViewModel : ViewModel() {
private val _count = MutableLiveData<Int>()
val count: LiveData<Int> = _count
private var currentCount = 0
fun onPageResume() {
refreshCount()
}
fun onPagePause() {
// 页面暂停时,做一些业务处理
// 比如停止轮询、保存状态等
}
private fun refreshCount() {
currentCount++
_count.value = currentCount
}
}
Activity
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var tvCount: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvCount = findViewById(R.id.tvCount)
observeData()
addLifecycleObserver()
}
private fun observeData() {
viewModel.count.observe(this) { count ->
tvCount.text = "count = $count"
}
}
private fun addLifecycleObserver() {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
viewModel.onPageResume()
}
override fun onPause(owner: LifecycleOwner) {
viewModel.onPagePause()
}
})
}
}
这段代码完整体现了两个方向:
1. UI → ViewModel
Activity onResume
↓
viewModel.onPageResume()
↓
ViewModel 刷新 count
2. ViewModel → UI
ViewModel 更新 count LiveData
↓
LiveData 通知 Activity
↓
Activity 更新 TextView
MVVM 里要分清两个方向。
第一,页面生命周期事件是 UI 层自己的能力,比如 Activity 的 onResume、onPause。ViewModel 不能主动感知这些生命周期。如果业务需要在 onResume 时刷新数据,应该由 Activity 或 Fragment 监听生命周期,然后主动调用
viewModel.onPageResume()。这属于 UI → ViewModel。第二,ViewModel 执行业务后会更新 LiveData,Activity 通过
observe(this)观察数据变化。这里的this是传给 LiveData 的 LifecycleOwner,作用是让 LiveData 根据页面生命周期自动分发和解绑数据,不是让 ViewModel 持有 Activity。这个方向属于 ViewModel → UI。所以,ViewModel 不感知页面生命周期,也不直接操作 UI。UI 负责感知生命周期和渲染页面,ViewModel 负责处理业务和暴露状态。
十一、一句话记忆
生命周期事件从 UI 进 ViewModel,数据状态从 ViewModel 回 UI。
再简化:
UI 负责感知事件
VM 负责处理业务
LiveData 负责通知变化
UI 负责渲染页面
核心区别:
onResume 调 vm 方法:UI → VM,是事件通知
observe LiveData:VM → UI,是数据通知