协程与 Android 生命周期:该何时取消、何时保留
文章目录第 1 章 生命周期为什么约束协程第 2 章 lifecycleScope短 UI 副作用踩坑第 3 章 viewModelScope 与 Fragment 取 VM踩坑第 4 章 repeatOnLifecycle后台停止收集踩坑第 5 章 取消链从 UI 到 Repository第 6 章 SupervisorJob 与并行任务动手练练习 1Scope 选型A · 基础练习 2viewLifecycleOwnerB · 进阶练习 3repeatOnLifecycleD · Android 实践练习 4旋转行为C · 综合练习 5反模式B · 进阶练习 6WhileSubscribedA · 基础进阶阅读可选追问链 #1lifecycleScope vs viewModelScope 追问链 #2Fragment 旋转 collect 会断吗 追问链 #3为何弃用 launchWhenStarted ⭐追问链 #4CancellationException 要 catch 吗 完整链路一句通相关推荐第 1 章 生命周期为什么约束协程Activity/Fragment 会创建、显示、隐藏、销毁。协程若比界面活得更久可能更新已销毁的 View → 崩溃或泄漏后台白白耗电 collect 热流旋转屏重复发请求 → 列表闪白原则能放 ViewModel 的异步逻辑不要只塞在lifecycleScopeUI Scope 负责「收集状态 短 UI 副作用」。Scope绑定对象取消时机lifecycleScopeLifecycleOwneronDestroyviewModelScopeViewModelonCleared()viewLifecycleOwner.lifecycleScopeFragment 的 View 生命周期onDestroyView第 2 章 lifecycleScope短 UI 副作用classScanActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)lifecycleScope.launch{delay(300)showHintSnackbar()// 短 UI 反馈}}}权限结果、动画结束后再刷 Snackbar 等与界面同寿命的操作适合lifecycleScope。数据加载应进 ViewModelclassTodoViewModel(privatevalrepo:TodoRepository,):androidx.lifecycle.ViewModel(){funload(){viewModelScope.launch{valitemsrepo.loadAll()// _state.update { it.copy(items items) } // 见《Flow 与 StateFlow》}}}旋转屏销毁 Activity 但ViewModel 保留viewModelScope内请求可继续——这是 MVVM 里「数据不因旋转而重拉」的基础。踩坑在lifecycleScope里调 Repository 拉列表——旋转可能取消请求或重复发起。第 3 章 viewModelScope 与 Fragment 取 VMclassTodoListFragment: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。viewLifecycleOwnerFragment 的 View 可能比 Fragment 实例先死收集必须绑 View 生命周期。踩坑用lifecycleScopeFragment 实例collect在onDestroyView之后仍可能运行。第 4 章 repeatOnLifecycle后台停止收集StateFlow是热流ViewModel 一直在 emit。若在lifecycleScope.launch { flow.collect }里直接 collectApp 进后台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时viewModelScope的SupervisorJob使一个失败不取消兄弟——但仍应在子协程内runCatching转错误态viewModelScope.launch{launch{loadHeader()}launch{loadFooter()}}不要在已取消的 scope 里更新 UI检查isActive或在runCatching里忽略CancellationException。动手练练习 1Scope 选型A · 基础目标lifecycle vs viewModel。要求列表数据加载用哪个 ScopeSnackbar 延迟显示用哪个各一句。练习 2viewLifecycleOwnerB · 进阶目标Fragment 收集。要求Fragment collect Flow 为何用viewLifecycleOwner而不是thisFragment练习 3repeatOnLifecycleD · Android 实践目标后台省电。要求说明不用repeatOnLifecycle时App 进后台 collect 仍运行的问题。练习 4旋转行为C · 综合目标配置变更。要求旋转屏时lifecycleScope与viewModelScope各自怎样正在进行的load()会不会中断练习 5反模式B · 进阶目标识别坏写法。要求lifecycleScope.launch { repo.loadAll(); adapter.submit(list) }有两处问题各写一句。练习 6WhileSubscribedA · 基础目标与生命周期配合。要求stateIn(WhileSubscribed(5000))在无 UI 订阅 5 秒后做什么有什么好处进阶阅读可选追问链 #1lifecycleScope vs viewModelScope 参考回答前者绑界面销毁即取消适合短 UI后者绑 ViewModel跨配置变更适合业务与 StateFlow 生产。追问链 #2Fragment 旋转 collect 会断吗 参考回答repeatOnLifecycle在 STOPPED 取消 collectSTARTED 重启ViewModel 与 state 保留不中断业务协程。追问链 #3为何弃用 launchWhenStarted ⭐参考回答暂停而非取消时行为易错repeatOnLifecycle语义清晰与 Flow 配合更好。追问链 #4CancellationException 要 catch 吗 参考回答一般向上抛不要当业务错误吞掉runCatching要注意重新抛出取消。完整链路一句通业务进 viewModelScope 生产 StateFlow→Fragment by viewModels() 拿同一 VM→viewLifecycleOwner.repeatOnLifecycle(STARTED) 内 collect→后台自动停 collect→销毁时 onCleared / CancellationException 收尾。相关推荐Kotlin 作用域函数let/apply 怎么选Kotlin 语法与空安全从零的第一课