Android 基础补强 B17|LiveData、StateFlow 和 Lifecycle 分别解决什么问题
阅读《第一行代码》第 13 章时会遇到 Lifecycles 和 LiveData,进入现代 Compose 示例又常看到 StateFlow。容易产生的误解是:换成 Flow,就不需要考虑生命周期;或者旧项目使用 LiveData,说明它已经不能维护。
本篇比较它们的职责,重点回查 13.3 的 Lifecycles 与 13.4 的 LiveData,再连接 D05 的状态组织。迁移依据应是业务和维护需要,不是简单按出现先后给工具排座次。
生命周期描述什么时候适合观察
Activity 和 Fragment 会经历创建、可见、交互、停止和销毁等变化。观察界面数据时,需要考虑当前界面是否仍存在、是否处在合适状态,而不是只考虑数据有没有更新。
Fragment 尤其要区分对象寿命和 View 寿命。对象可能留在返回栈中,旧 View 却已经销毁。给旧 binding 写入新数据不一定立刻崩溃,也可能只是延长持有时间或者更新了用户看不到的对象。生命周期感知观察要绑定正确的 owner。生命周期感知协程
LiveData 提供生命周期感知的数据观察
LiveData 常用于把可观察数据交给 Android UI,观察者与 LifecycleOwner 关联,在活跃状态接收更新。以下是 Fragment 的局部用法:已经有 viewModel、articles 与 render,代码在 onViewCreated 中注册。
kotlin
viewModel.articles.observe(viewLifecycleOwner) { articles ->
render(articles)
}
这里的关键是 viewLifecycleOwner,不只是选择了 LiveData。如果使用不依赖 owner 的永久观察方式,则需要自己明确解除时机。对象能够被观察,并不意味着所有使用方式都自动安全。LiveData 概览
更新方式也要按线程和时序理解。不要把连续快速更新当作可靠的事件队列;UI 状态只要求最终显示最新内容时,与必须逐一处理的操作消息并不是同一个需求。
StateFlow 持有状态,Lifecycle 决定界面如何收集
StateFlow 是具有当前值的热流,适合表示屏幕当前状态。它不会因为被声明为 ViewModel 属性,就自动知道某个 Fragment 的 View 已经销毁。Android UI 需要选择生命周期感知的收集方式。
下面是 Fragment 内的局部示例,需要 Lifecycle KTX 和协程,ViewModel 暴露 uiState,render 只更新当前 View。实际使用时补齐导入并在 View 销毁时清理 binding;示例未在此次写稿中编译。
kotlin
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
render(state)
}
}
}
当生命周期低于指定状态时,内部工作取消;重新达到条件时再启动。Compose 常使用 collectAsStateWithLifecycle 把流接到界面状态。收集的启停与网络请求是否重新发出是两层设计,取决于上游流和加载入口,不能把恢复收集直接等同于请求缓存。Compose 状态收集
冷流与共享策略影响工作次数
一个普通 flow 构建器中的逻辑可能在每次被收集时执行;共享后的状态流又有不同寿命。若把请求直接放在冷流里,页面重新收集可能再次发起请求。若使用 stateIn 和订阅驱动策略,还要理解何时启动、停止以及保留哪份状态。
因此排查重复请求时,依次查看三个位置:哪个业务事件决定加载;上游是否每次收集重新执行;共享作用域活多久。不要只在 UI 中加一个全局 Boolean 防重复,因为它可能连真正需要的重新加载也一起挡住。StateFlow 与 SharedFlow
状态对象也应有清楚的更新方式。对外暴露只读 StateFlow,内部更新不可变状态,能减少页面随意改写数据。普通可变集合原地修改后,既可能影响相等判断,也可能让观察者难以识别更新,应按数据模型与观察规则设计。
状态不等于一次性事件
文章列表、搜索词、加载状态适合被新观察者重新读取;一次导航动作或短暂提示是否应该在页面重建后重放,则需要独立业务规则。简单把 Boolean 从 false 改为 true,可能让返回页面后再次执行同一个动作。
先明确"用户是否已经消费""丢失后是否需要补发""多个观察者分别负责什么",再选择状态表达或事件通道。没有一个类名能够替你同时保证所有事件的恰好一次执行。
故障实验
让 Fake 上游在每次开始收集时记录一次日志,进入页面、切后台、返回,再重建 View。分别观察订阅次数和实际请求次数。预期能解释每次变化来自生命周期还是上游策略,而不是只看到请求多就笼统归咎于重组。
再在测试环境中把观察绑定到 Fragment 自身,执行销毁 View 但保留 Fragment 的场景。恢复正确绑定后,检查旧 View 不再接收更新。日志可以帮助定位,但对内存问题还需要引用链证据。
三道原创面试自测
换成 StateFlow 是否自动解决生命周期? 不会,UI 仍要按生命周期收集。追问"为什么放在 ViewModel 还不够",ViewModel 和具体 View 的寿命不同。
页面恢复后为什么重新请求? 可能是冷流重新执行、共享策略重启或加载入口重复调用。追问"怎么定位",分别记录业务入口、订阅开始和网络请求,而不是只数界面函数执行次数。
为什么不立即把全部 LiveData 改成 Flow? 迁移需要收益和验证范围。追问"先改哪里",可从需要协程组合且边界明确的一条链路开始,保留行为对照,避免一次改动整个应用。
完成本篇后,能够画出数据寿命、观察寿命和页面寿命三者关系,才算把旧书 Jetpack 内容与当前实践真正连接起来。