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)
        }
    }
}
💡 我的习惯: 只要是在 Activity/Fragment 内部启动的协程,优先用 lifecycleScope。它比 GlobalScope 安全得多,也比自己维护 Job 省心。

但这里有个细节要注意: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)
        }
    }
}
⚠️ 我曾经踩过的坑: 在 ViewModel 里用 lifecycleScope 而不是 viewModelScope。结果屏幕旋转后,协程被取消了,数据没加载完。后来改成 viewModelScope,问题解决。记住:ViewModel 里永远用 viewModelScope。

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 里取消要优雅多少倍?

🔑 核心要点: repeatOnLifecycle 最适合和 Flow 的 collect 配合使用。它能保证:
- 界面可见时才收集数据
- 界面不可见时自动取消收集
- 界面重新可见时重新开始收集

13.4 协程在 Activity/Fragment 中的安全启动

前面讲了三个工具,现在我们来总结一下「安全启动」的最佳实践。

我个人总结了一个简单的决策树:

  1. 任务和 UI 无关? → 用 viewModelScope(在 ViewModel 里)
  2. 任务和 UI 有关,且需要跟随整个生命周期? → 用 lifecycleScope
  3. 任务和 UI 有关,但只需要在可见时执行? → 用 lifecycleScope + repeatOnLifecycle(STARTED)
  4. 任务需要在界面可见且可交互时执行? → 用 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)
        }
    }
}
💡 注意: 在 Fragment 中,尽量使用 viewLifecycleOwner 而不是 this。因为 Fragment 的 view 可能被销毁但 Fragment 本身还在。viewLifecycleOwner 的生命周期和 Fragment 的 View 绑定,更安全。

13.5 知识体系图

下面这张图总结了本章的核心知识结构:

协程与Android生命周期核心知识体系 lifecycleScope Activity/Fragment 自带 viewModelScope ViewModel 专属 repeatOnLifecycle 精确阶段控制 特性 • 默认 Dispatchers.Main • onDestroy 时自动取消 • 适合 UI 相关任务 特性 • 屏幕旋转不取消 • onCleared 时取消 • 适合数据加载任务 特性 • 指定 Lifecycle.State • 进入时启动,离开时取消 • 适合 Flow 收集 安全启动最佳实践 ① UI无关任务 → viewModelScope ② UI相关全生命周期 → lifecycleScope ③ UI相关仅可见时 → lifecycleScope + repeatOnLifecycle(STARTED) ④ UI相关可交互时 → lifecycleScope + repeatOnLifecycle(RESUMED)

13.6 避坑指南

最后分享几个我实际项目中遇到的坑:

  • 不要在 Fragment 中用 this.lifecycleScope:应该用 viewLifecycleOwner.lifecycleScope。否则 View 销毁后协程还在跑,可能操作已销毁的 View 导致崩溃。
  • repeatOnLifecycle 要配合 lifecycleScope 使用:它本身不是作用域,只是一个挂起函数。必须放在 lifecycleScope 的 launch 里调用。
  • viewModelScope 不是万能的:如果 ViewModel 被清除(比如 Activity 真正 finish),协程也会取消。如果需要后台任务,考虑 WorkManager。
  • Flow 的 collect 要放在 repeatOnLifecycle 里面:而不是外面。否则 collect 会一直挂起,repeatOnLifecycle 的取消逻辑不会生效。
⚠️ 我曾经犯过的错: 在 Fragment 的 onCreate 里用 lifecycleScope 启动了一个协程,里面做了网络请求。结果 Fragment 的 View 被销毁了,协程还在跑,回调时更新 UI 直接空指针。后来改成在 onViewCreated 里启动,并且用 viewLifecycleOwner,问题解决。

好了,关于协程与生命周期的内容就到这里。记住:生命周期管理是协程在 Android 中安全使用的基石。lifecycleScope、viewModelScope、repeatOnLifecycle 这三个工具用好了,你的代码会既安全又优雅。


公众号:蓝海资料掘金营,微信deep3321