协程与 Android 生命周期:该何时取消、何时保留

文章目录

  • 第 1 章 生命周期为什么约束协程
  • 第 2 章 lifecycleScope:短 UI 副作用
      • 踩坑
  • 第 3 章 viewModelScope 与 Fragment 取 VM
      • 踩坑
  • 第 4 章 repeatOnLifecycle:后台停止收集
      • 踩坑
  • 第 5 章 取消链:从 UI 到 Repository
  • 第 6 章 SupervisorJob 与并行任务
  • 动手练
        • 练习 1:Scope 选型(A · 基础)
        • 练习 2:viewLifecycleOwner(B · 进阶)
        • 练习 3:repeatOnLifecycle(D · Android 实践)
        • 练习 4:旋转行为(C · 综合)
        • 练习 5:反模式(B · 进阶)
        • 练习 6:WhileSubscribed(A · 基础)
  • 进阶阅读(可选)
      • 追问链 #1:lifecycleScope vs viewModelScope? 🔥
      • 追问链 #2:Fragment 旋转 collect 会断吗? 🔥
      • 追问链 #3:为何弃用 launchWhenStarted? ⭐
      • 追问链 #4:CancellationException 要 catch 吗? 💡
  • 完整链路一句通
  • 相关推荐

第 1 章 生命周期为什么约束协程

Activity/Fragment 会创建、显示、隐藏、销毁。协程若比界面活得更久,可能:

  1. 更新已销毁的 View → 崩溃或泄漏
  2. 后台白白耗电 collect 热流
  3. 旋转屏重复发请求 → 列表闪白

原则:能放 ViewModel 的异步逻辑不要只塞在lifecycleScope;UI Scope 负责「收集状态 + 短 UI 副作用」。

Scope绑定对象取消时机
lifecycleScopeLifecycleOwneronDestroy
viewModelScopeViewModelonCleared()
viewLifecycleOwner.lifecycleScopeFragment 的 View 生命周期onDestroyView

第 2 章 lifecycleScope:短 UI 副作用

classScanActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)lifecycleScope.launch{delay(300)showHintSnackbar()// 短 UI 反馈}}}

权限结果、动画结束后再刷 Snackbar 等与界面同寿命的操作适合lifecycleScope

数据加载应进 ViewModel:

classTodoViewModel(privatevalrepo:TodoRepository,):androidx.lifecycle.ViewModel(){funload(){viewModelScope.launch{valitems=repo.loadAll()// _state.update { it.copy(items = items) } // 见《Flow 与 StateFlow》}}}

旋转屏销毁 Activity 但ViewModel 保留viewModelScope内请求可继续——这是 MVVM 里「数据不因旋转而重拉」的基础。

踩坑

  • lifecycleScope里调 Repository 拉列表——旋转可能取消请求或重复发起。

第 3 章 viewModelScope 与 Fragment 取 VM

classTodoListFragment:Fragment(){privatevalviewModel:TodoViewModelbyviewModels()overridefunonViewCreated(view:View,savedInstanceState:Bundle?){super.onViewCreated(view,savedInstanceState)observeState()}privatefunobserveState(){viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){viewModel.state.collect{ui->// 绑定列表}}}}}
  • by viewModels()保证旋转后仍是同一TodoViewModel
  • viewLifecycleOwner:Fragment 的 View 可能比 Fragment 实例先死,收集必须绑 View 生命周期。

踩坑

  • lifecycleScope(Fragment 实例)collect,在onDestroyView之后仍可能运行。

第 4 章 repeatOnLifecycle:后台停止收集

StateFlow是热流,ViewModel 一直在 emit。若在lifecycleScope.launch { flow.collect }直接 collect,App 进后台(onStop)后协程仍活着,白白耗电,还可能碰已销毁 View。

viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){viewModel.state.collect{state->render(state)}}}

进入STOPPED时 cancel collect 块;回到STARTED重新 collect,并立刻拿到StateFlow当前值。

ComposecollectAsStateWithLifecycle()封装同样语义。

踩坑

  • 已废弃的launchWhenStarted:新代码用repeatOnLifecycle

第 5 章 取消链:从 UI 到 Repository

用户返回 / finish() → Activity onDestroy → lifecycleScope 取消 ViewModelStore 清空 → ViewModel.onCleared() → viewModelScope 取消 → 子协程收到 CancellationException repeatOnLifecycle 离开 STARTED → collect 协程取消(上游 stateIn 可按 WhileSubscribed 停)

suspend函数应在取消时尽快结束;阻塞 IO 要可中断;invokeOnCancellation解绑回调(《suspend 与 Continuation》)。


第 6 章 SupervisorJob 与并行任务

ViewModel 内并行多个独立launch时,viewModelScopeSupervisorJob使一个失败不取消兄弟——但仍应在子协程内runCatching转错误态:

viewModelScope.launch{launch{loadHeader()}launch{loadFooter()}}

不要在已取消的 scope 里更新 UI;检查isActive或在runCatching里忽略CancellationException


动手练

练习 1:Scope 选型(A · 基础)

目标:lifecycle vs viewModel。

要求:列表数据加载用哪个 Scope?Snackbar 延迟显示用哪个?各一句。

练习 2:viewLifecycleOwner(B · 进阶)

目标:Fragment 收集。

要求:Fragment collect Flow 为何用viewLifecycleOwner而不是this(Fragment)?

练习 3:repeatOnLifecycle(D · Android 实践)

目标:后台省电。

要求:说明不用repeatOnLifecycle时,App 进后台 collect 仍运行的问题。

练习 4:旋转行为(C · 综合)

目标:配置变更。

要求:旋转屏时lifecycleScopeviewModelScope各自怎样?正在进行的load()会不会中断?

练习 5:反模式(B · 进阶)

目标:识别坏写法。

要求lifecycleScope.launch { repo.loadAll(); adapter.submit(list) }有两处问题,各写一句。

练习 6:WhileSubscribed(A · 基础)

目标:与生命周期配合。

要求stateIn(WhileSubscribed(5000))在无 UI 订阅 5 秒后做什么?有什么好处?


进阶阅读(可选)

追问链 #1:lifecycleScope vs viewModelScope? 🔥

参考回答:前者绑界面,销毁即取消,适合短 UI;后者绑 ViewModel,跨配置变更,适合业务与 StateFlow 生产。

追问链 #2:Fragment 旋转 collect 会断吗? 🔥

参考回答repeatOnLifecycle在 STOPPED 取消 collect,STARTED 重启;ViewModel 与 state 保留,不中断业务协程。

追问链 #3:为何弃用 launchWhenStarted? ⭐

参考回答:暂停而非取消时行为易错;repeatOnLifecycle语义清晰,与 Flow 配合更好。

追问链 #4:CancellationException 要 catch 吗? 💡

参考回答:一般向上抛,不要当业务错误吞掉;runCatching要注意重新抛出取消。


完整链路一句通

业务进 viewModelScope 生产 StateFlowFragment by viewModels() 拿同一 VMviewLifecycleOwner.repeatOnLifecycle(STARTED) 内 collect后台自动停 collect销毁时 onCleared / CancellationException 收尾


相关推荐

  • Kotlin 作用域函数:let/apply 怎么选

  • Kotlin 语法与空安全:从零的第一课