Fragment 不属于"四大组件"(Activity / Service / BroadcastReceiver / ContentProvider),它没有自己独立的生命周期入口,必须依附在 Activity(或另一个 Fragment)之中。但它太重要了——现代 Android 主流的"单 Activity 架构"正是建立在 Fragment 之上。
一、Fragment:可复用的"界面碎片"
在 Activity 篇 里我们打过一个比方:
Activity 是相框,Fragment 是相框里可以随时替换的照片。
Activity 是用户看到的"窗口",但一个窗口里的内容往往需要拆分和复用:
- 手机上是「列表页」和「详情页」两个界面,平板上却希望它们并排显示在同一个屏幕里;
- 一个底部导航栏 App,点击不同 Tab 切换内容,但顶部栏、底部栏始终不变。
如果一个界面就开一个 Activity,上面这些需求会非常难写。Fragment 就是为此而生:它是一段可复用的、带有自己生命周期和界面的 UI 单元,可以被组合进 Activity,也可以在运行时动态添加、替换、移除。
一个最简单的 Fragment 长这样:
class ProfileFragment : Fragment(R.layout.fragment_profile) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 在这里拿到布局里的控件,绑定数据、设置监听
val nameText = view.findViewById<TextView>(R.id.name)
nameText.text = "张三"
}
}
注意: Fragment 不需要在 AndroidManifest.xml 中注册(这点和四大组件完全不同),它由宿主 Activity 在代码或布局中加载。
Fragment 与 Activity 的关系
- 一个 Activity 可以容纳一个或多个 Fragment;
- Fragment 的生命周期完全依赖于宿主 Activity——Activity 销毁了,里面的 Fragment 也会跟着销毁;
- 我们日常用的 AppCompatActivity → 继承自 FragmentActivity,正是 FragmentActivity 提供了管理 Fragment 的能力;
- Jetpack 的 Navigation 组件就是基于"单 Activity + 多 Fragment"架构设计的。
注意版本与依赖: 现在开发应优先使用 AndroidX 的 androidx.fragment.app.Fragment,而不是已废弃的平台版 android.app.Fragment。本文所有内容都基于 AndroidX Fragment。
二、单 Activity 架构
早期 Android 是"一个界面一个 Activity"。但随着平板、折叠屏、大屏设备出现,这种方式越来越力不从心:
| 平板分屏(列表 + 详情并排) | 难以实现,Activity 无法并排 | 两个 Fragment 放进同一界面即可 |
| 界面复用(同一块 UI 多处使用) | 复制粘贴布局和逻辑 | 写一个 Fragment 到处用 |
| 切换内容但保留外壳(底部导航) | 频繁启动 Activity,外壳重复 | 一个 Activity 壳,切换 Fragment |
| 页面间转场动画 | 受限于 Activity 转场 | Fragment 事务可灵活控制动画 |
于是现代应用的主流架构变成了:
一个 Activity 作为"外壳" + 多个 Fragment 作为"内容页",页面跳转交给 Navigation 组件管理。
这就是 单 Activity 架构(Single-Activity Architecture),也是 Google 目前推荐的做法。
三、Fragment生命周期
理解 Fragment,最核心的就是它的生命周期。它比 Activity 复杂,原因在于:
Fragment 有"两条命"——它自身的实例(Fragment 对象)和它的视图(View)是分开管理的。
这一点至关重要,后面会专门展开。先看完整的回调顺序。
3.1 创建阶段的回调顺序
从添加到 Activity,到界面显示,Fragment 依次经历:
onAttach() ─── Fragment 与宿主 Activity 关联,此时可以拿到 Context
onCreate() ─── 创建 Fragment 实例,初始化非视图数据(注意:此时还没有 View)
onCreateView() ─── 创建并返回 Fragment 的视图(加载布局)
onViewCreated() ─── 视图已创建完成,在这里操作控件最安全
onViewStateRestored() ─── 恢复视图保存的状态(如输入框文字)
onStart() ─── Fragment 可见
onResume() ─── Fragment 到达前台,可与用户交互
← 用户正常使用中 →
3.2 销毁阶段的回调顺序
3.3 每个回调该做什么?
| onAttach() | 与 Activity 关联时 | 获取 Context,做与宿主相关的初始化 |
| onCreate() | 创建实例时 | 初始化与视图无关的数据(如读取参数 arguments) |
| onCreateView() | 需要绘制界面时 | 加载并返回布局(推荐用构造函数传布局 ID 替代手写此方法) |
| onViewCreated() | 视图创建完成后 | 绑定控件、设置监听、观察 ViewModel/LiveData |
| onStart() | 即将可见 | 注册 UI 相关监听 |
| onResume() | 到达前台 | 开启动画、请求相机/传感器 |
| onPause() | 即将失去前台 | 暂停动画、提交临时数据 |
| onStop() | 完全不可见 | 释放重量级资源 |
| onDestroyView() | 视图被销毁 | 清理对 View 的引用(如置空 binding),注销视图相关监听 |
| onDestroy() | 实例被销毁 | 释放剩余资源 |
| onDetach() | 与 Activity 解除关联 | 清理对 Context 的引用 |
注意: 不要在 onCreateView() 之前(如 onCreate())操作视图控件,因为那时 View 还不存在。绑定控件、设置点击事件等,统一放在 onViewCreated() 里最安全。
四、Fragment 的"两条命":实例 vs 视图
这是 Fragment 生命周期里最容易出 Bug的地方,必须单独讲清楚。
普通情况下 Fragment 走完整流程没问题。但有一种常见场景会让"实例还活着、视图却已经死了":把 Fragment 放进 BackStack(返回栈)后,又导航到另一个 Fragment。
此时旧 Fragment 会执行到 onDestroyView()——它的视图被销毁了,但 Fragment 对象本身没有销毁(还留在返回栈里等待回来)。当用户按返回键回到它时,会重新执行 onCreateView() 创建一个全新的视图,但 onCreate() 不会再走一遍。
首次进入: onCreate → onCreateView → onViewCreated → … → onResume
被覆盖: onPause → onStop → onDestroyView ← 视图没了,实例还在
返回回来: onCreateView → onViewCreated → … → onResume ← 重建了新视图
这带来两个实际后果:
4.1 视图引用必须及时清理(避免内存泄漏)
如果你用 ViewBinding 持有了视图引用,必须在 onDestroyView() 中置空,否则旧视图无法被回收:
class ProfileFragment : Fragment(R.layout.fragment_profile) {
private var _binding: FragmentProfileBinding? = null
// 只在视图存在期间有效
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentProfileBinding.bind(view)
binding.name.text = "张三"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // 关键:视图销毁了,引用也要清掉
}
}
4.2 观察数据要用"视图的生命周期"
观察 LiveData 时,不要用 this(Fragment 的生命周期),而要用 viewLifecycleOwner(视图的生命周期):
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 正确示例:绑定到视图生命周期
viewModel.user.observe(viewLifecycleOwner) { user ->
binding.name.text = user.name
}
}
为什么? 如果用 this,当 Fragment 视图被销毁(onDestroyView)但实例仍存活时,观察者不会被移除。等数据更新时,回调里访问的却是已被销毁的旧视图,轻则内存泄漏,重则崩溃。viewLifecycleOwner 会随视图一起销毁,自动解除观察。
五、FragmentManager 与事务
Activity 用栈管理多个 Activity;类似地,FragmentManager 负责管理一个宿主里的所有 Fragment。
获取方式:
// 在 Activity 中
supportFragmentManager
// 在 Fragment 中管理它的子 Fragment
childFragmentManager
// 在 Fragment 中获取宿主的 FragmentManager
parentFragmentManager
所有对 Fragment 的增删改,都通过一个**事务(Transaction)**完成,并且必须 commit():
supportFragmentManager.commit {
setReorderingAllowed(true) // 推荐开启,优化生命周期调度
replace<ProfileFragment>(R.id.container) // 替换容器里的 Fragment
addToBackStack("profile") // 加入返回栈,按返回键可回退
}
5.1 add / replace / remove 的区别
| add() | 往容器里添加一个 Fragment | 多次 add 会叠加,旧的仍在 |
| replace() | 移除容器里现有 Fragment,再添加新的 | 最常用 |
| remove() | 移除指定 Fragment | |
| show() / hide() | 隐藏/显示,不销毁视图 | 适合需要保留状态的 Tab 切换 |
5.2 返回栈(Back Stack)
调用 addToBackStack() 后,这次事务会被记录到返回栈。用户按返回键时,系统会回退这次事务(撤销 replace,恢复上一个 Fragment),而不是直接退出 Activity。这正是 Fragment 能模拟"页面跳转"的关键。
提示: 直接手写 FragmentManager 事务在简单场景没问题,但页面多、跳转复杂时,强烈建议改用 Navigation 组件(见第七节),它把这些事务封装成了可视化的导航图。
六、Fragment 通信
Fragment 应该是自包含、可复用的,所以它不应该直接持有另一个 Fragment 或 Activity 的引用去调用对方方法(那样耦合太紧,复用就破坏了)。Jetpack 推荐了几种解耦的通信方式。
6.1 共享 ViewModel(同一 Activity 内,最推荐)
同一个 Activity 下的多个 Fragment,可以共享作用域为 Activity 的 ViewModel,通过它间接通信:
// 两个 Fragment 都这样获取,拿到的是同一个 ViewModel 实例
private val sharedViewModel: SharedViewModel by activityViewModels()
// A Fragment 写入
sharedViewModel.select(item)
// B Fragment 观察
sharedViewModel.selectedItem.observe(viewLifecycleOwner) { item ->
// 收到 A 的选择
}
这是平板"列表 + 详情"场景的标准做法:列表 Fragment 选中某项,详情 Fragment 自动更新。
6.2 Fragment Result API(一次性结果传递)
如果只是 A → B 传一个"结果"(类似 Activity Result),用 setFragmentResult / setFragmentResultListener:
// 接收方(在 onCreate / onViewCreated 中注册监听)
setFragmentResultListener("requestKey") { _, bundle ->
val result = bundle.getString("data")
}
// 发送方
setFragmentResult("requestKey", bundleOf("data" to "hello"))
6.3 与宿主 Activity 通信
Fragment 需要通知 Activity 时,优先用共享 ViewModel;轻量场景也可以让 Activity 实现一个接口,Fragment 在 onAttach() 时拿到回调对象。但接口回调方式耦合较高,新代码更推荐 ViewModel 方案。
七、Navigation 组件
手写事务和返回栈在复杂应用里容易出错。Jetpack Navigation 把这些封装成了一张可视化的导航图(Nav Graph):
- NavGraph:一张 XML(或 Compose DSL)图,描述有哪些目的地(Fragment)以及它们之间怎么跳;
- NavHostFragment:放在 Activity 布局里的"容器",所有 Fragment 在此显示;
- NavController:负责执行跳转、管理返回栈。
// 在 Fragment 中跳转到详情页,并安全地传参
findNavController().navigate(
HomeFragmentDirections.actionHomeToDetail(itemId = 42)
)
Navigation 的优势:
- 类型安全传参(Safe Args 插件,编译期检查参数);
- 自动处理返回栈和返回键;
- 统一管理转场动画、深层链接(Deep Link);
- 与底部导航栏、抽屉菜单一键集成。
现状提示: 在 Jetpack Compose 中,导航的主体已逐渐变成 Navigation Compose(直接在可组合函数之间导航,不再需要 Fragment)。但只要项目还使用传统 View 体系或 Fragment,“Navigation + Fragment” 仍是主流且完全推荐的方案。
八、几种特殊的 Fragment
| DialogFragment | 用 Fragment 的方式实现对话框,能正确处理屏幕旋转,推荐替代直接 new 一个 Dialog |
| BottomSheetDialogFragment | 从底部弹出的面板(来自 Material 库) |
| PreferenceFragmentCompat | 快速搭建"设置页",配合 XML 自动生成设置项 |
| NavHostFragment | Navigation 组件的容器 Fragment |
参考来源:
- Android 官方文档 – Fragments
- Fragment 生命周期
- 在 Fragment 之间通信
- Navigation 组件






