B08_Fragment事务通信与双生命周期

Android 基础补强 B08|Fragment 还在,为什么 binding 已经不能用了

摘要:用列表进入详情再返回的过程,理解 Fragment 事务、返回栈、View 生命周期及结果通信,避免对旧 View 更新和重复添加页面。

标签:Android、Fragment、生命周期、ViewBinding、第一行代码

本文对应《第一行代码》第 3 版第 5 章,以及 28 天课程 D17 的传统界面练习。Fragment 是受宿主管理的界面组件,但 Fragment 对象存在和它的 View 存在不是同一件事。下文使用独立的 FragmentActivity 练习入口,不改主项目的 Navigation 3;代码片段需要补齐布局、依赖和工厂,尚未编译。

1. 用返回操作理解两段寿命

列表 Fragment A 进入详情 B,如果事务允许返回,A 可能仍由 FragmentManager 保留,而它之前创建的 View 已被销毁。返回时 A 可以重新创建 View。于是成员变量 _binding 若一直持有旧 Binding,就等于持续引用旧界面树;异步回调还可能更新一份已经不显示的控件。

Fragment 生命周期描述组件本身,View 生命周期描述本次界面实例。访问 Binding 应限制在 View 创建之后、销毁之前。页面的业务数据可以放在 ViewModel 或 Repository,控件引用不能跟随较长寿命的业务对象一起保存。Fragment 生命周期

这也解释了为什么仅把 Binding 声明成可空还不够。需要在销毁时释放引用,让收集界面状态的任务跟随 viewLifecycleOwner,并避免长期对象保留 View、Activity 或带着它们的闭包。空安全和对象寿命是两类问题。

2. 一次 View 创建对应一次收集关系

假设 fragment_articles.xml 生成 FragmentArticlesBinding,包含列表 articleList;articleAdapter 来自 B07,viewModel.state 发出不可变列表状态。以下代码位于 Fragment 内,依赖 Fragment、Lifecycle、RecyclerView 与 ViewBinding。

kotlin 复制代码
private var binding: FragmentArticlesBinding? = null

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
    val current = FragmentArticlesBinding.inflate(inflater, container, false)
    binding = current
    return current.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val current = checkNotNull(binding)
    current.articleList.layoutManager = LinearLayoutManager(requireContext())
    current.articleList.adapter = articleAdapter
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.state.collect { articleAdapter.submitList(it.items) }
        }
    }
}

override fun onDestroyView() {
    binding?.articleList?.adapter = null
    binding = null
    super.onDestroyView()
}

生命周期低于指定状态时内部收集停止,重新达到状态时再启动。重新收集不应自动等同于重复发送网络请求,是否刷新由数据层策略决定。上例把 Adapter 从旧 RecyclerView 脱离,也让引用边界更清晰;如果 Adapter 本身长期持有旧 Activity,仍需要修复,清一个字段不是万能证明。

3. 事务与返回栈需要显式说明

Activity 首次创建练习入口时,只在 savedInstanceState == null 时添加初始 Fragment,因为恢复时 FragmentManager 可以恢复已有组件。无条件重复添加可能导致旋转后出现重叠或重复实例。进入详情用事务替换容器,若期望返回列表,要加入返回栈。

下面假设宿主布局包含 FragmentContainerView,ID 为 fragment_container,使用 Fragment KTX 的类形式操作。代码放在有效的用户交互时刻,不能拿它解决宿主已经保存状态后的任意异步导航。

kotlin 复制代码
supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace<ArticleDetailFragment>(
        R.id.fragment_container,
        args = bundleOf("articleId" to selectedId)
    )
    addToBackStack("article_detail")
}

commit 安排事务稍后执行,不意味着下一行就能找到新 View。commitNow 是另一种即时提交方式,并不能与加入返回栈随意混用。遇到状态已保存后的提交问题,应检查触发时机与恢复策略,而不是默认改成允许状态丢失。Fragment 事务说明

传入文章 ID 让详情按身份查询数据,避免把可变的大对象与控件引用塞进参数。无效 ID 应进入明确的不可用页面,这样恢复过程和正常点击过程可以使用相同入口。

4. 通信先区分持续状态与一次结果

列表和详情共享收藏状态,适合读取同一个 Repository 或合适作用域的 ViewModel。详情不必持有列表 Fragment,然后直接寻找列表按钮改色。对于"选择完一个筛选项返回"的一次结果,可以使用 Fragment Result API;发送方和监听方必须使用同一个 FragmentManager 和同一个键。Fragment 通信指南

监听对象达到可交付的生命周期状态后才处理结果。结果键并不是持久消息队列,同一键在尚未交付时重复设置可能只保留最新结果。因此不要用它保存整个收藏历史,也不要把"发送过一次"当作数据库已经持久化。

共享 ViewModel 同样需要明确作用域。Activity 级共享可能适合两个同宿主页面,但范围过大也会让无关页面共享状态。选择范围时应回答"谁需要共同读写这份状态、什么时候应该结束",而不是只为了方便调用。

5. 进入、返回、旋转的实验顺序

给 Fragment 和每次创建的 root 记录不同实例标识。列表进入详情再返回,预期能观察到组件与 View 寿命并非总是同步;旧 View 的收集在销毁后结束,新 View 有自己的收集。重复十次,检查是否出现重复观察和旧对象更新日志。

随后在列表旋转设备,确认不会重复添加初始 Fragment。最后从详情修改收藏再返回,预期列表通过共享数据源显示最新状态,而不是依赖保存下来的 View 引用。若结果不一致,先查询数据,再检查作用域及收集生命周期。以上为实验预期,实际日志需要自行执行后补充。

6. 原创面试问答与追问

问一:Fragment 没销毁,为什么要清 Binding? 它的 View 可能已销毁,Binding 引用的是那次 View。追问:只用 Fragment 的生命周期收集够吗?界面更新应绑定 View 的生命周期。

问二:commit 之后能立即操作新控件吗? 不能假定事务已经执行。追问:是否统一换 commitNow?不应,返回栈与时机约束不同,应在组件正常 View 回调中初始化。

问三:Fragment Result 和共享 ViewModel 怎么选? 一次结果与持续共享状态需求不同。追问:收藏归属哪个?长期事实应由数据层持久化,共享状态持有者组织展示。

问答为原创整理,并非面试鸭题库原题。验收时能画出一条进入、返回与 View 重建时间线,解释引用何时有效、事务怎样恢复、数据如何共享,就完成了本篇第 5 章基础补强。

相关推荐
Kapaseker1 小时前
Android 以后可能不会再有横竖屏适配了
android·kotlin
墨天梦1 小时前
B13_通知图片相机与影音
android·数码相机·kotlin
事圆则缓2 小时前
Android 一次性事件怎么设计:Channel、SharedFlow 还是 UI State?
android·ui
Android系统攻城狮14 小时前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
mmsx18 小时前
Android 地图数据链路:从 GDAL、KML 到多引擎适配
android·kotlin
2601_9689007718 小时前
大模型版本回归评估实战:从能力指标到额度口径的完整链路
android·数据挖掘·回归
ao-weilai20 小时前
MySQL数据库:基本查询
android·数据库·mysql
事圆则缓21 小时前
Kotlin 泛型方差实战:out、in、星投影与类型擦除
android·开发语言·kotlin