UI 层生命周期安全收集
源:Lifecycle-aware flow collection
在 Android 开发中,直接从 UI 层(Activity/Fragment/Compose)收集 Flow 是一项极具风险的操作。由于 UI 具有复杂的生命周期,不当的收集方式会导致资源泄露(后台持续计算)、电量损耗以及非法状态下的 UI 更新(引发 Crash)。
为什么直接启动协程是危险的?
1. 资源泄露与 CPU 浪费
使用 lifecycleScope.launch 收集流时,即使 Activity 进入后台(onStop),协程依然在运行。如果流的上游在执行昂贵的计算或网络请求,这些操作将继续消耗 CPU 和电量,即便用户根本看不见界面。
2. 状态更新的安全隐患
在 onStop 之后更新 UI 或执行 Fragment 事务,极易触发 IllegalStateException。
技术演进:从挂起到取消
第一代:launchWhenStarted (不推荐)
launchWhenStarted 会在生命周期低于 STARTED 时挂起协程。
- 致命缺点:它仅仅是挂起,并没有取消。上游生产者(如数据库监听)无法感知下游已停止处理,会继续发射数据填满缓冲区,造成资源浪费。
第二代:repeatOnLifecycle (View 系统标准)
这是目前 View 系统中最安全、最推荐的方案。其核心逻辑是:当生命周期低于指定状态时,彻底取消协程;当生命周期恢复时,重新启动协程。
kotlin
viewLifecycleOwner.lifecycleScope.launch {
// ⭐️ 核心:在 STARTED 时启动,在 STOPPED 时取消
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// 由于协程被取消,上游的 Flow 也会随之停止生产
viewModel.uiState.collect { state ->
updateUI(state)
}
}
}kotlin
// 如果只有一个流需要收集,可以使用 flowWithLifecycle
viewModel.uiState
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.onEach { state -> updateUI(state) }
.launchIn(viewLifecycleOwner.lifecycleScope)现代方案:Jetpack Compose 中的收集
在 Compose 中,由于重组(Recomposition)的存在,收集流需要更加精细的生命周期管理。
依赖说明
必须引入依赖:androidx.lifecycle:lifecycle-runtime-compose
kotlin
@Composable
fun UserScreen(viewModel: UserViewModel) {
// ⭐️ collectAsStateWithLifecycle 会在 Composable 退出
// 或 Activity 进入后台时自动处理 repeatOnLifecycle(STARTED) 逻辑
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
UserContent(uiState)
}kotlin
@Composable
fun UserScreen(viewModel: UserViewModel) {
// ❌ 错误:collectAsState 只感知 Composition,不感知外部生命周期
// 当 App 进入后台,后台收集依然活跃,浪费 CPU
val uiState by viewModel.uiState.collectAsState()
}全链路协同:WhileSubscribed 策略
UI 层的“安全收集”必须配合 ViewModel 层的“共享策略”才能发挥最大效力。
kotlin
val uiState = repository.dataFlow
.stateIn(
scope = viewModelScope,
// ⭐️ 当所有订阅者消失 5 秒后,停止上游流
started = SharingStarted.WhileSubscribed(5000),
initialValue = Loading
)5 秒旋转屏幕容错机制
- 场景:当用户旋转屏幕时,旧 Activity 销毁(订阅者 0),新 Activity 立即创建(订阅者 1)。
- 逻辑:由于有 5000ms 的缓冲期,上游流不会因为短暂的重订阅而停止,从而避免了重复发起耗时网络请求或数据库查询。
核心开发准则
- 始终感知取消:使用
repeatOnLifecycle的最大意义在于它利用了“取消”这一信号。如果你的上游流不支持取消(没有挂起点),安全收集将失效。 - 区分 STARTED 与 RESUMED:大多数情况下使用
STARTED以确保在 UI 部分可见时即可开始刷新。仅在必须确保 UI 处于前台焦点(无遮挡)时才使用RESUMED。 - 避免在 collect 中执行耗时操作:UI 层的
collect应该只负责更新 UI 状态。复杂的逻辑应下沉到 ViewModel 的操作符链中。 - 配合热流使用:安全收集最适合配合
StateFlow使用,因为重启动后,StateFlow的粘性能让 UI 立即恢复到最新状态。