文章目录
- 第 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 会创建、显示、隐藏、销毁。协程若比界面活得更久,可能:
- 更新已销毁的 View → 崩溃或泄漏
- 后台白白耗电 collect 热流
- 旋转屏重复发请求 → 列表闪白
原则:能放 ViewModel 的异步逻辑不要只塞在lifecycleScope;UI Scope 负责「收集状态 + 短 UI 副作用」。
| Scope | 绑定对象 | 取消时机 |
|---|---|---|
lifecycleScope | LifecycleOwner | onDestroy |
viewModelScope | ViewModel | onCleared() |
viewLifecycleOwner.lifecycleScope | Fragment 的 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当前值。
Compose:collectAsStateWithLifecycle()封装同样语义。
踩坑
- 已废弃的
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时,viewModelScope的SupervisorJob使一个失败不取消兄弟——但仍应在子协程内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 · 综合)
目标:配置变更。
要求:旋转屏时lifecycleScope与viewModelScope各自怎样?正在进行的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 生产 StateFlow→Fragment by viewModels() 拿同一 VM→viewLifecycleOwner.repeatOnLifecycle(STARTED) 内 collect→后台自动停 collect→销毁时 onCleared / CancellationException 收尾。
相关推荐
Kotlin 作用域函数:let/apply 怎么选
Kotlin 语法与空安全:从零的第一课