13、协程与Android生命周期:lifecycleScope、viewModelScope的使用、lifecycle.repeatOnLifecycle、协程在Activity/Fragment中的安全启动
协程在Android里最大的坑是什么?
我个人的答案是:生命周期。
你想想看,一个网络请求发出去,用户突然旋转屏幕,Activity重建了。如果协程还在跑,轻则内存泄漏,重则直接崩溃。嗯,这个问题我早期踩过不少次。
好在Google早就帮我们想好了方案。lifecycleScope、viewModelScope、repeatOnLifecycle,这三个东西就是专门用来解决「协程与生命周期同步」的。今天我们就把它彻底讲透。
13.1 lifecycleScope:跟着Activity/Fragment走的协程作用域
lifecycleScope 是 LifecycleOwner 的扩展属性。说白了,每个 Activity 和 Fragment 都自带一个协程作用域。
它的生命周期绑定规则很简单:
- Activity:在 onDestroy 时自动取消
- Fragment:在 onDestroy 时自动取消
用法非常直接:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在 lifecycleScope 中启动协程
lifecycleScope.launch {
// 这个协程会在 Activity 销毁时自动取消
val data = fetchData()
updateUI(data)
}
}
}
但这里有个细节要注意:lifecycleScope 默认使用 Dispatchers.Main.immediate。也就是说,launch 里面的代码默认跑在主线程。如果你要做网络请求或数据库操作,记得切线程:
lifecycleScope.launch {
withContext(Dispatchers.IO) {
// 耗时操作
}
// 回到主线程更新 UI
}
13.2 viewModelScope:ViewModel 专属的协程作用域
viewModelScope 是 ViewModel 的扩展属性。它和 lifecycleScope 最大的区别是:它不关心 Activity 是否重建,只关心 ViewModel 是否被清除。
什么意思呢?
用户旋转屏幕,Activity 重建了,但 ViewModel 还在。viewModelScope 里的协程不会取消。只有 ViewModel 的 onCleared() 被调用时(比如 Activity 真正 finish),协程才会取消。
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = repository.fetchData()
_uiState.value = UiState.Success(data)
}
}
}
13.3 lifecycle.repeatOnLifecycle:精确控制协程的生命周期阶段
lifecycleScope 只能保证协程在销毁时取消。但有时候我们需要更精细的控制——比如只在 onStart 到 onStop 之间执行,或者只在 onResume 到 onPause 之间执行。
这时候就要用 repeatOnLifecycle。
它的作用很简单:当 Lifecycle 进入指定状态时启动协程,离开该状态时取消协程。并且如果状态再次进入,会重新启动。
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 只在 onStart 到 onStop 之间收集 Flow
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.someFlow.collect { value ->
updateUI(value)
}
}
}
}
}
你想想看,这比手动在 onStart 里启动、onStop 里取消要优雅多少倍?
- 界面可见时才收集数据
- 界面不可见时自动取消收集
- 界面重新可见时重新开始收集
13.4 协程在 Activity/Fragment 中的安全启动
前面讲了三个工具,现在我们来总结一下「安全启动」的最佳实践。
我个人总结了一个简单的决策树:
- 任务和 UI 无关? → 用 viewModelScope(在 ViewModel 里)
- 任务和 UI 有关,且需要跟随整个生命周期? → 用 lifecycleScope
- 任务和 UI 有关,但只需要在可见时执行? → 用 lifecycleScope + repeatOnLifecycle(STARTED)
- 任务需要在界面可见且可交互时执行? → 用 lifecycleScope + repeatOnLifecycle(RESUMED)
来看一个完整的例子:
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 1. 收集 Flow:只在可见时收集
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
renderState(state)
}
}
}
// 2. 一次性任务:跟随 Fragment 生命周期
viewLifecycleOwner.lifecycleScope.launch {
val config = loadConfig()
applyConfig(config)
}
}
}
13.5 知识体系图
下面这张图总结了本章的核心知识结构:
13.6 避坑指南
最后分享几个我实际项目中遇到的坑:
- 不要在 Fragment 中用 this.lifecycleScope:应该用 viewLifecycleOwner.lifecycleScope。否则 View 销毁后协程还在跑,可能操作已销毁的 View 导致崩溃。
- repeatOnLifecycle 要配合 lifecycleScope 使用:它本身不是作用域,只是一个挂起函数。必须放在 lifecycleScope 的 launch 里调用。
- viewModelScope 不是万能的:如果 ViewModel 被清除(比如 Activity 真正 finish),协程也会取消。如果需要后台任务,考虑 WorkManager。
- Flow 的 collect 要放在 repeatOnLifecycle 里面:而不是外面。否则 collect 会一直挂起,repeatOnLifecycle 的取消逻辑不会生效。
好了,关于协程与生命周期的内容就到这里。记住:生命周期管理是协程在 Android 中安全使用的基石。lifecycleScope、viewModelScope、repeatOnLifecycle 这三个工具用好了,你的代码会既安全又优雅。
公众号:蓝海资料掘金营,微信deep3321