欢迎光临
我们一直在努力

Fragment 全解

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 方案
平板分屏(列表 + 详情并排) 难以实现,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 销毁阶段的回调顺序

  • onPause() ─── 失去前台焦点
  • onStop() ─── 完全不可见
  • onDestroyView() ─── 销毁视图(但 Fragment 实例还在)
  • onDestroy() ─── 销毁 Fragment 实例
  • onDetach() ─── Fragment 与 Activity 解除关联
  • 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 组件
    赞(0)
    未经允许不得转载:171主机测评 » Fragment 全解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址