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 的关系可以类比为"乐高积木"和"底板": 是底板, 是拼在上面的积木块。底板负责在屏幕上占一个位置,积木块负责填充具体内容。

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() 一次性执行。

分步1 / 4

① 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

可交互
宿主 Activity 的 FrameLayoutR.id.fragment_container(容器,等 Fragment 入驻)返回栈(Back Stack)▲ 栈底 · 按返回键弹回CrimeListFragment列表页 FragmentCrimeFragment详情页 Fragment

第 1 / 4 步 · ① Activity 刚启动:setContentView 后 fragment_container 还空着,尚未提交任何事务

点击播放,看 FragmentManager 一笔事务如何把 Fragment 装进容器、replace 换走、再被返回键弹回;容器里高亮的那张永远是屏幕上正显示的 Fragment。可暂停、单步、拖进度逐帧观察。

FragmentManager 事务:add 把 Fragment 装进 R.id.fragment_container;replace 换成另一个、 addToBackStack 把旧的推入返回栈;按返回键 popBackStack 把它弹回容器。容器里高亮的那张 就是用户当前看到的 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”

状态所有者FragmentManager、Fragment 实例与 viewLifecycleOwner
受控状态fragment 状态、View binding、事务和容器内容
触发事件创建、替换、旋转及 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管理器:事件轨迹

选择一次状态转换1 / 5

不变量:View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复

交付证据:Fragment/View 实例 ID、事务日志、回调序列和泄漏检查

实验三:章专属反例与同输入恢复

注入“Fragment 保存已销毁 View binding,异步回调写入旧界面”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有Fragment/View 实例 ID、事务日志、回调序列和泄漏检查一起恢复才算修复。

Fault · cancel · restore

UI fragment与fragment管理器:反例与恢复

故障:Fragment 保存已销毁 View binding,异步回调写入旧界面

1. 冻结输入一致

第 1 次使用相同 SDK、设备配置、初始状态与用户事件

2. 注入边界一致

保持正常输入不变,仅注入“Fragment 保存已销毁 View binding,异步回调写入旧界面”

3. 检查所有者一致

View 销毁后不再接收界面回调,Fragment 状态仍可按合同恢复

4. 核对结果一致

Fragment/View 实例 ID、事务日志、回调序列和泄漏检查

讨论

评论区加载中…