UI fragment与fragment管理器
UI fragment与fragment管理器:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。
学习目标
- 能沿“UI fragment与fragment管理器”的用户事件解释Android组件、状态所有者、线程与销毁边界。
- 能围绕“掌握 Fragment 的生命周期、FragmentManager 的事务操作、以及 Fragment 与 Activity 的通信模式。”改出一个可运行结果,并用前后状态而非组件数量验收。
- 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
- 能用构建指纹、用户操作、状态快照、原始日志和行为断言独立重放结论,并标明第四版机制与现代targetSdk政策的边界。
为什么需要 Fragment?
想象你在写一个平板应用:左边是列表,右边是详情。如果只有 Activity 这一个"页面"单位,你只能把列表和详情硬塞进同一个布局里——代码会像千层饼一样越堆越厚,想复用其中一部分到别的页面?基本不可能。
Fragment 就是来解决这个痛点的:它把 UI 拆成一个个可以灵活组装、独立管理的"积木块"。一个 Activity 可以容纳多个 Fragment,Fragment 自己也可以被不同的 Activity 复用。平板上的列表+详情布局,用两个 Fragment 各管各的,代码清爽,复用也轻松。
没有 Fragment 会怎样?大屏适配会变成噩梦——你要么为每种屏幕尺寸写一套 Activity,要么在一个 Activity 里写满 if (isTablet)。Fragment 让 UI 模块化成为可能。
Fragment 是怎么工作的
Fragment 是什么
一个 Fragment 就是一个可复用的 UI 片段,它有自己的布局、自己的生命周期、自己的事件处理逻辑,但它必须寄生在一个宿主 Activity 里面才能显示。
Fragment 和 Activity 的关系可以类比为"乐高积木"和"底板":↡Activity 是 Android 里代表一个屏幕页面的组件,Fragment 必须附着在 Activity 上才能显示。 是底板,↡Fragment 是 Android 里一个可复用的 UI 片段,必须由 Activity 承载,拥有自己的生命周期和布局。 是拼在上面的积木块。底板负责在屏幕上占一个位置,积木块负责填充具体内容。
Fragment 的生命周期
Fragment 有自己的生命周期,但它的生命周期被宿主 Activity 的生命周期牵着走。Activity 暂停,里面的 Fragment 也会暂停;Activity 销毁,Fragment 也会跟着销毁。
下面是 Fragment 生命周期中最常打交道的方法:
| 方法 | 触发时机 | 典型的活 |
|---|---|---|
onAttach() | Fragment 被绑定到 Activity | 获取宿主引用 |
onCreate() | Fragment 被创建 | 初始化非 UI 数据 |
onCreateView() | Fragment 即将渲染布局 | inflate 布局文件 |
onViewCreated() | 布局创建完毕后 | findViewById、设置监听器 |
onStart() | Fragment 即将可见 | 注册观察者 |
onResume() | Fragment 获得焦点 | 开始动画、相机预览 |
onPause() | Fragment 失去焦点 | 暂停动画、释放相机 |
onStop() | Fragment 不可见 | 注销观察者 |
onDestroyView() | Fragment 布局被销毁 | 清理 View 引用 |
onDestroy() | Fragment 被销毁 | 释放所有资源 |
onDetach() | Fragment 与 Activity 解绑 | 取消宿主引用 |
核心记忆:onCreateView() 创建 View,onViewCreated() 操作 View,onDestroyView() 清理 View。
FragmentManager:谁来管 Fragment
你不需要手动管理 Fragment 的生死——交给 FragmentManager。它是一个"Fragment 管家",负责:
- 把 Fragment 加入 Activity(
add()) - 把 Fragment 换成另一个(
replace()) - 把 Fragment 从屏幕上去掉(
remove()) - 管理回退栈(按返回键回到上一个 Fragment)
所有这些操作都通过 FragmentTransaction 执行,而且遵循"打包-提交"模式:把想做的操作一条条写进事务,最后 commit() 一次性执行。
① newInstance 创建 Fragment
通过工厂方法 newInstance(crimeId) 创建 Fragment 实例,将参数封装进
arguments Bundle——确保系统重建 Fragment
时参数不丢失。调用方只需传业务参数,不用关心 Bundle 内部细节。
代码逐段拆解
第一步:创建 Fragment 类
import { Term } from "@/components/mdx/term";
import { Glossary, GlossaryItem } from "@/components/mdx/glossary";
import { Objectives } from "@/components/mdx/objectives";
import { Step, Stepper } from "@/components/mdx/stepper";
import { Callout } from "@/components/mdx/callout";
import { FragmentTransactionDiagram } from "@/components/mdx/diagrams/fragment-transaction-diagram";
import { Answer, Exercises } from "@/components/mdx/exercises";
import { Attribution } from "@/components/mdx/attribution";
import androidx.fragment.app.Fragment
class CrimeFragment : Fragment(R.layout.fragment_crime) {
// 构造器里直接传布局 ID,省掉重写 onCreateView
}使用 Fragment(R.layout.fragment_crime) 这个构造函数是 Fragment 1.1.0 引入的便捷方式。它内部自动帮你做了 LayoutInflater.inflate() 的事情。等价于传统写法:
class CrimeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_crime, container, false)
}
}第二步:用 FragmentManager 把 Fragment 放到 Activity 里
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction
.add(R.id.fragment_container, CrimeFragment())
.commit()
}
}R.id.fragment_container 是你在 Activity 布局里预留的"Fragment 插槽"——通常是一个 FrameLayout。
第 1 / 4 步 · ① Activity 刚启动:setContentView 后 fragment_container 还空着,尚未提交任何事务
点击播放,看 FragmentManager 一笔事务如何把 Fragment 装进容器、replace 换走、再被返回键弹回;容器里高亮的那张永远是屏幕上正显示的 Fragment。可暂停、单步、拖进度逐帧观察。
第三步:用 Arguments 传参数给 Fragment
Fragment 的构造函数不能带参数(系统重建 Fragment 时会用无参构造器)。正确的传参方式是用 Bundle,这叫做 Fragment Arguments 模式:
class CrimeFragment : Fragment() {
companion object {
private const val ARG_CRIME_ID = "crime_id"
fun newInstance(crimeId: UUID): CrimeFragment {
val args = Bundle().apply {
putSerializable(ARG_CRIME_ID, crimeId)
}
return CrimeFragment().apply {
arguments = args
}
}
}
private val crimeId: UUID by lazy {
arguments?.getSerializable(ARG_CRIME_ID) as UUID
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// arguments 在 onCreate 里已经可用
}
}newInstance() 是一个工厂方法——把传参逻辑封装进去,外部调用者只需 CrimeFragment.newInstance(someId),不用关心内部怎么传的 Bundle。
第四步:Fragment 与 Activity 通信
方案一:接口回调(经典方式)
class CrimeFragment : Fragment() {
interface Callbacks {
fun onCrimeSelected(crimeId: UUID)
}
private var callbacks: Callbacks? = null
override fun onAttach(context: Context) {
super.onAttach(context)
callbacks = context as? Callbacks
}
override fun onDetach() {
super.onDetach()
callbacks = null
}
}方案二:共享 ViewModel(现代推荐方式)
class CrimeFragment : Fragment() {
private val sharedViewModel: CrimeViewModel by activityViewModels()
}by activityViewModels() 拿到的 ViewModel 跟宿主 Activity 是同一个实例,所以同一 Activity 里的所有 Fragment 天然共享同一个 ViewModel——这是最干净的数据共享方案。
容易踩的坑
小结
- Fragment 是可复用的 UI 模块,寄生在 Activity 里,有自己独立但受控的生命周期
- FragmentManager + FragmentTransaction 是操作 Fragment 的唯一入口(add/replace/remove)
- 参数传递用 Arguments Bundle + newInstance 工厂模式,不要用自定义构造器
- Fragment 与 Activity 通信推荐用共享 ViewModel;经典回调也可行但耦合度高
练习
问题 1:“第8章 UI Fragments and the Fragment Manager”覆盖哪些正式节点和项目主线?
问题 2:怎样建立本页最小可执行实验?
问题 3:为什么只在正常点击路径运行不能证明完成?
问题 4:怎样设计能推翻当前实现的反例?
问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?
问题 6:本页达到独立交接标准需要什么?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Activity
Android 里代表一个全屏页面的组件。像一个舞台,Fragment 是上面的演员——舞台提供场地,演员负责表演。
- Fragment
寄生在 Activity 里的可复用 UI 模块。有自己的布局、生命周期和逻辑,但不能独立显示——必须有一个宿主 Activity。
- FragmentManager
Fragment 的管家。负责把 Fragment 往 Activity 里加、换、删,管理回退栈。通过
supportFragmentManager获取。- FragmentTransaction
FragmentManager 执行操作时的一个"打包事务"。把要做的 add/replace/remove 等操作写到事务里,最后 commit 一次性提交。像银行转账——要么全做,要么全不做。
- Bundle
Android 里的"键值对背包",用于在组件间传递数据。Fragment 用 Bundle(Arguments)接收参数,因为构造器不能带参数。
“UI fragment与fragment管理器”不使用未获授权的纸书正文;InformIT出版信息与授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。
为什么“UI fragment与fragment管理器”必须回到可观察状态
“UI fragment与fragment管理器”的学习结果不是记住类名,而是能预测“掌握 Fragment 的生命周期、FragmentManager 的事务操作、以及 Fragment 与 Activity 的通信模式。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。
第四版机制逐项深读
8. UI Fragments and the Fragment Manager
在“UI fragment与fragment管理器”中,验证“8. UI Fragments and the Fragment Manager”在旋转、后台恢复和快速连点下比较fragment列表、back stack与目标数据,只出现一次导航副作用。
The Need for UI Flexibility
在“UI fragment与fragment管理器”中,“The Need for UI Flexibility”若依赖第四版时期API,应分开说明原书机制与现代平台政策,并用Android官方文档核对迁移边界。
Introducing Fragments
在“UI fragment与fragment管理器”中,分析“Introducing Fragments”要标出事务提交、参数写入、目标结果和返回栈变化,避免用活动中的临时字段传递可恢复状态。
Starting CriminalIntent
在“UI fragment与fragment管理器”中,分析“Starting CriminalIntent”区分编译、运行时、布局、线程与资源问题,避免在后续连锁报错处停止定位。
Creating a Data Class
在“UI fragment与fragment管理器”中,验证“Creating a Data Class”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Creating a UI Fragment
在“UI fragment与fragment管理器”中,“Creating a UI Fragment”由FragmentManager保存结构和返回栈,而Fragment实例与其View拥有不同生命周期;视图绑定必须在onDestroyView前释放。
Hosting a UI Fragment
在“UI fragment与fragment管理器”中,“Hosting a UI Fragment”的容器替换不是简单换画面;状态保存后提交、重复tag与嵌套管理器都会改变恢复结果。
Application Architecture with Fragments
在“UI fragment与fragment管理器”中,验证“Application Architecture with Fragments”用同一事件序列比较旋转前后状态和副作用次数;界面相同但重复写入仍是不正确。
“UI fragment与fragment管理器”验收回顾
“UI fragment与fragment管理器”只有在构建指纹、用户操作、状态快照、原始日志和行为断言能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。
← 上一页:SDK版本与兼容性 · 下一页:使用RecyclerView显示列表 →
章专属可重放状态实验
先预测“创建、替换、旋转及 onDestroyView”发生后,FragmentManager、Fragment 实例与 viewLifecycleOwner应怎样改变fragment 状态、View binding、事务和容器内容;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。
实验一:所有者—状态—结果合同
选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。
Owner · state · observable result
UI fragment与fragment管理器:状态合同
区分 Fragment 实例与 Fragment View 生命周期,并由 FragmentManager 恢复结构
验证场景
第四版正式目录节点
ui-fragments · 正常任务
8. UI Fragments and the Fragment Manager:固定 SDK、设备配置和初始状态,触发“创建、替换、旋转及 onDestroyView”
冻结入口:8. UI Fragments and the Fragment Manager
记录FragmentManager、Fragment 实例与 viewLifecycleOwner的初始fragment 状态、View binding、事务和容器内容
观察:Fragment/View 实例 ID、事务日志、回调序列和泄漏检查中的“8. UI Fragments and the Fragment Manager”轨迹
预期:由FragmentManager、Fragment 实例与 viewLifecycleOwner提交fragment 状态、View binding、事务和容器内容,并持续满足“View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复”
实验二:事件与生命周期轨迹
沿五次转换逐步执行“创建、替换、旋转及 onDestroyView”。每一步只允许FragmentManager、Fragment 实例与 viewLifecycleOwner按职责提交状态,并持续核对“View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复”。
Deterministic event replay
UI fragment与fragment管理器:事件轨迹
不变量:View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复
交付证据:Fragment/View 实例 ID、事务日志、回调序列和泄漏检查
实验三:章专属反例与同输入恢复
注入“Fragment 保存已销毁 View binding,异步回调写入旧界面”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有Fragment/View 实例 ID、事务日志、回调序列和泄漏检查一起恢复才算修复。
Fault · cancel · restore
UI fragment与fragment管理器:反例与恢复
故障:Fragment 保存已销毁 View binding,异步回调写入旧界面
第 1 次使用相同 SDK、设备配置、初始状态与用户事件
保持正常输入不变,仅注入“Fragment 保存已销毁 View binding,异步回调写入旧界面”
View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复
Fragment/View 实例 ID、事务日志、回调序列和泄漏检查