Skip to content

Fragment

Fragment是Android中的一种组件,直译为“碎片”或“片段”,可以看作是Activity的模块化部分,Fragment 可以将 Activity 视图拆分为多个区块进行模块化地管理 ,避免了 Activity 视图代码过度臃肿混乱。在官方的定义中Fragment 表示应用界面中可重复使用的一部分。

  • fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件
  • fragment 不能独立存在。它们必须由 activity 或其他 fragment 托管
  • fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。
  • Fragment可以被多个Activity复用,提高了代码的复用率。在模块化开发中,一个Fragment可以代表一个独立的业务模块,从而在不同的地方重复使用。

添加方式

静态添加

定义:直接在 XML 布局文件中声明 Fragment,适用于固定展示的视图组件。这种方式添加后一般不可在运行时删除或更换。

实现步骤

  1. 创建 Fragment 的布局文件(如 fragment_my.xml)。
  2. 在 Activity 或父 Fragment 的布局文件中使用 <fragment> 标签。
  3. 通过 android:name 属性指定 Fragment 类的全限定名(包名 + 类名)。

示例代码

xml
<!-- activity_main.xml -->  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  

    <!-- 静态添加 Fragment -->  
    <fragment  
        android:id="@+id/staticFragment"  
        android:name="com.example.app.MyFragment"  
        android:layout_width="match_parent"  
        android:layout_height="200dp" />  
</LinearLayout>

注意事项

  • 不可动态修改:无法通过代码移除或替换已静态添加的 Fragment。
  • 必须声明唯一标识:需设置 android:idandroid:tag 属性以便查找。
  • 生命周期依赖:Fragment 会随 Activity 的 onCreate() 自动初始化。

动态添加

定义:通过代码管理 Fragment 的增删替换,适用于交互场景(如 Tab 切换、页面导航)。

实现步骤

  1. 在布局文件中定义容器(如 FrameLayout)。
  2. 通过 FragmentManager 启动事务(beginTransaction())。
  3. 使用事务方法(add()/replace()/remove())操作 Fragment。
  4. 提交事务(commit())。

示例代码

kotlin
// 1. 定义容器布局  
<!-- activity_main.xml -->  
<FrameLayout  
    android:id="@+id/fragmentContainer"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" />  

// 2. 动态添加 Fragment  
class MainActivity : AppCompatActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  

        // 创建 Fragment 实例  
        val dynamicFragment = MyFragment()  

        // 执行事务  
        supportFragmentManager.beginTransaction().apply {  
            replace(R.id.fragmentContainer, dynamicFragment, "DynamicFragmentTag")  
            setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) // 过渡动画  
            addToBackStack("transaction_tag") // 加入回退栈  
            commit()  
        }  
    }  
}

核心方法

方法说明
add()向容器中叠加 Fragment,保留原有视图(适合多 Fragment 层叠场景)。
replace()清空容器后添加新 Fragment(适合单视图切换场景)。
remove()从容器中移除已添加的 Fragment。
addToBackStack()将事务加入回退栈,用户按返回键时可回滚操作。

注意事项

  • 容器要求:容器必须是 ViewGroup(如 FrameLayoutConstraintLayout)。
  • 事务提交时机:避免在 onSaveInstanceState() 后调用 commit()(可能导致状态丢失)。
  • 回退栈管理:若未调用 addToBackStack(),返回键会直接关闭 Activity 而非回退 Fragment。
  • 异步安全:使用 commitAllowingStateLoss() 在极端场景(如应用后台)提交事务,但可能丢失状态。

生命周期

Fragment的生命周期状态有以下五个:

  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED
  • DESTROYED

Fragment的生命周期回调方法有12个,比Activity多了好些个,如图所示: QQ_1744891360756

Fragment 的 12 个生命周期方法

生命周期方法触发时机典型用途
1. onAttach()Fragment 与宿主 Activity 建立关联时获取 Activity 引用,初始化通信接口
2. onCreate()Fragment 初始化时(视图创建前)初始化非视图数据,恢复保存状态
3. onCreateView()创建 Fragment 的 UI 视图时加载布局文件,绑定基础视图组件
4. onViewCreated()视图层级创建完成后立即调用视图初始化、设置监听器、加载动态数据
5. onActivityCreated()宿主 Activity 的 onCreate() 完成时(已过时,建议用 onViewCreated 替代)执行需要 Activity 完全初始化的操作
6. onStart()Fragment 可见但不可交互时启动轻量级后台任务
7. onResume()Fragment 进入可交互状态注册传感器监听、启动动画
8. onPause()Fragment 即将失去焦点时提交未保存数据、释放高耗能资源
9. onStop()Fragment 完全不可见时停止所有后台任务
10. onDestroyView()视图层级被销毁时解绑视图引用(如 ViewBinding)、清理视图相关资源
11. onDestroy()Fragment 即将被销毁时释放全局资源、断开数据库连接
12. onDetach()Fragment 与 Activity 解除关联时清空 Activity 引用防止内存泄漏

生命周期的影响因素

Fragment 生命周期与宿主同步的吗?并不是,Fragment 的生命周期主要受「宿主」、「事务」、「setRetainInstance() API」三个因素影响:当宿主生命周期发生变化时,会触发 Fragment 状态转移到 宿主的最新状态。不过,使用事务和 setRetainInstance() API 也可以使 Fragment 在一定程度上与宿主状态不同步(需要注意:宿主依然在一定程度上形成约束)。

宿主如何改变 Fragment 状态

结论:当宿主生命周期发生变化时,Fragment 的状态会同步到宿主的状态。 原因:宿主生命周期回调中会调用 FragmentManager 中一系列 dispatchXXX() 方法来触发 Fragment 状态转移。

kotlin
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);
    ...
    mFragments.dispatchCreate(); // 最终调用 FragmentManager#dispatchCreate()
}

其中状态改变的关系图如下: 首次启动

调用顺序组件回调方法
1⭐ActivityonCreate
2✅FragmentonAttach
3✅FragmentonCreate
4✅FragmentonCreateView
5✅FragmentonViewCreated
6⭐ActivityonStart
7✅FragmentonActivityCreated
8✅FragmentonStart
9⭐ActivityonResume
10✅FragmentonResume

退出

调用顺序组件回调方法
1⭐ActivityonPause
2✅FragmentonPause
3⭐ActivityonStop
4✅FragmentonStop
5⭐ActivityonDestroy
6✅FragmentonDestroyView
7✅FragmentonDestroy
8✅FragmentonDetach

回到桌面

调用顺序组件回调方法
1⭐ActivityonPause
2✅FragmentonPause
3⭐ActivityonStop
4✅FragmentonStop

返回

调用顺序组件回调方法
1⭐ActivityonStart
2✅FragmentonStart
3⭐ActivityonResume
4✅FragmentonResume

可以看出,Activity 的生命周期的变化会带动 Fragment 的一个甚至多个生命周期的变化。

事务管理

在运行时,FragmentManager 可以通过 Fragment 执行添加、移除、替换以及其他操作,以响应用户互动。每组 Fragment 更改称为一个“事务”,并且可以使用 FragmentTransaction 类提供的 API 指定在事务内需执行何种操作。

使用事务 FragmentTransaction 可以动态改变 Fragment 状态,使得 Fragment 在一定程度脱离宿主的状态。不过,事务依然受到宿主状态约束,例如:当前 Activity 处于 STARTED 状态,那么 addFragment 不会使得 Fragment 进入 RESUME 状态。只有将来 Activity 进入 RESUME 状态时,才会同步 Fragment 到最新状态。

事务的操作

Fragment的事务操作有如下几个:

  • add & remove:Fragment 状态在 INITIALIZING 与 RESUMED 之间转移;
  • detach & attach: Fragment 状态在 CREATE 与 RESUMED 之间转移;
  • replace: 先移除所有 containerId 中的实例,再 add 一个 Fragment;
  • show & hide: 只控制 Fragment 隐藏或显示,不会触发状态转移,也不会销毁 Fragment 视图或实例;
  • hide & detach & remove 的区别: hide 不会销毁视图和实例、detach 只销毁视图不销毁实例、remove 会销毁实例(自然也销毁视图)。不过,如果 remove 的时候将事务添加到回退栈,那么 Fragment 实例就不会被销毁,只会销毁视图。

事务的提交

每个 FragmentTransaction 上的最终调用必须提交事务。commit() 调用会向 FragmentManager 发出信号,指明所有操作均已添加到事务中。FragmentTransaction 定义了 5 种提交方式:

提交方法是否允许状态丢失是否异步执行说明
commit()❌ 不允许✅ 是最常用的提交方式,如果在 onSaveInstanceState() 之后调用,可能会抛异常
commitAllowingStateLoss()✅ 允许✅ 是可在状态保存后调用,但有丢失状态的风险,适合不重要的 UI 更新
commitNow()❌ 不允许❌ 否立即执行事务(同步),只能在主线程使用,不能用于添加到回退栈的事务
commitNowAllowingStateLoss()✅ 允许❌ 否立即执行事务(同步),允许状态丢失,适用于紧急但非关键的界面变更
runOnCommit(Runnable)--在 commit 之后立即运行回调,常用于执行额外逻辑(API 24+)

实际运用

现在的Fragment基本上基于Jetpack Navigation Component 实现。

Fragment 与 Activity(以及 Fragment 之间)的数据传递

一、Fragment ⇄ Activity 数据传递

  1. 通过 Bundle 传递(单向初始化数据)适用场景:Activity 向 Fragment 传递初始参数

实现步骤

kotlin
// Activity 端  
val fragment = MyFragment().apply {  
    arguments = bundleOf("KEY_NAME" to "Alice", "KEY_AGE" to 25)  
}  
supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit()  

// Fragment 端  
val name = arguments?.getString("KEY_NAME")  
val age = arguments?.getInt("KEY_AGE", 0)
  1. 通过 ViewModel 共享数据(双向实时同步)
    适用场景:需要跨组件实时共享数据

实现步骤

kotlin
// 定义共享 ViewModel  
class SharedViewModel : ViewModel() {  
    val liveData = MutableLiveData<String>()  
}  

// Activity 端  
val viewModel by viewModels<SharedViewModel>()  
viewModel.liveData.value = "Hello from Activity"  

// Fragment 端  
val viewModel by activityViewModels<SharedViewModel>()  
viewModel.liveData.observe(viewLifecycleOwner) { data ->  
    textView.text = data  
}  

// Fragment 更新数据  
viewModel.liveData.value = "Hello from Fragment"

3. 接口回调(Fragment → Activity 通信)

适用场景:Fragment 向宿主 Activity 发送事件

实现步骤

kotlin
// 定义接口  
interface OnButtonClickListener {  
    fun onButtonClicked(message: String)  
}  

// Fragment 端  
class MyFragment : Fragment() {  
    private var listener: OnButtonClickListener? = null  

    override fun onAttach(context: Context) {  
        super.onAttach(context)  
        listener = context as? OnButtonClickListener  
    }  

    fun sendDataToActivity() {  
        listener?.onButtonClicked("Button Clicked!")  
    }  
}  

// Activity 端  
class MainActivity : AppCompatActivity(), OnButtonClickListener {  
    override fun onButtonClicked(message: String) {  
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()  
    }  
}

4. 直接访问公有方法

适用场景:Activity 主动获取 Fragment 数据

实现步骤

kotlin
// Fragment 定义公有方法  
fun getCurrentData(): String = "Data from Fragment"  

// Activity 端  
val fragment = supportFragmentManager.findFragmentById(R.id.fragment) as? MyFragment  
val data = fragment?.getCurrentData()

二、Fragment ⇄ Fragment 数据传递

1. 通过共享 ViewModel

适用场景:兄弟 Fragment 间实时通信

实现步骤

kotlin
// Fragment A 发送数据  
sharedViewModel.liveData.value = "Message from Fragment A"  

// Fragment B 接收数据  
sharedViewModel.liveData.observe(viewLifecycleOwner) {  
    textView.text = it  
}

2. 通过宿主 Activity 中转

实现步骤

kotlin
// Fragment A → Activity  
(activity as? MainActivity)?.receiveDataFromFragment("Data from A")  

// Activity → Fragment B  
supportFragmentManager.setFragmentResult("REQUEST_KEY", bundleOf("DATA_KEY" to data))  

// Fragment B 监听  
childFragmentManager.setFragmentResultListener("REQUEST_KEY", viewLifecycleOwner) { _, bundle ->  
    val data = bundle.getString("DATA_KEY")  
}

3. 使用 Fragment Result API

实现步骤

kotlin
// Fragment A 发送数据  
setFragmentResult("REQUEST_KEY", bundleOf("DATA_KEY" to "Hello"))  

// Fragment B 接收(在 onCreate 或 onViewCreated 中注册)  
parentFragmentManager.setFragmentResultListener("REQUEST_KEY", viewLifecycleOwner) { key, bundle ->  
    if (key == "REQUEST_KEY") {  
        val data = bundle.getString("DATA_KEY")  
    }  
}

三、方案对比

方法优点缺点适用场景
Bundle简单直接只能单向初始化时传递初始化参数传递
ViewModel数据持久化,双向实时同步需要理解生命周期作用域复杂数据共享
接口回调类型安全需要维护接口引用事件通知场景
Fragment Result API解耦组件需处理请求键管理简单结果返回

特殊场景处理

1. 状态保存与恢复

问题背景:当系统因内存不足或配置变更(如屏幕旋转)需要重建 Fragment 时,临时数据会丢失。需手动保存/恢复关键状态。

实现方案

kotlin
// 保存状态(在 Fragment 被销毁前调用)  
override fun onSaveInstanceState(outState: Bundle) {  
    super.onSaveInstanceState(outState)  
    outState.putString("inputText", editText.text.toString()) // 保存输入框文本  
}  

// 恢复状态(在视图重建后调用)  
override fun onViewStateRestored(savedInstanceState: Bundle?) {  
    super.onViewStateRestored(savedInstanceState)  
    val savedText = savedInstanceState?.getString("inputText")  
    editText.setText(savedText) // 恢复文本到输入框  
}

关键说明

  • onSaveInstanceState 触发时机:
    • 按下 Home 键/切换应用
    • 屏幕旋转等配置变更
    • 系统主动回收内存
  • onViewStateRestoredonCreateView 的区别:
    • onCreateView 仅创建视图,此时 Bundle 可能未恢复
    • onViewStateRestored 保证数据已恢复,适合初始化 UI 状态

2. 配置变更处理(屏幕旋转)

问题背景:默认情况下,屏幕旋转会触发 Activity/Fragment 重建,导致临时数据丢失。可通过两种方案处理:

方案一:允许重建但保存状态(推荐)

kotlin
// 不声明 configChanges,依赖 ViewModel + onSaveInstanceState 自动保存  
class MyFragment : Fragment() {  
    private val viewModel: MyViewModel by viewModels()  
    // 业务逻辑无需特殊处理  
}

方案二:禁止重建,手动调整布局

kotlin
// Step1: 在 AndroidManifest.xml 中声明  
<activity  
    android:name=".MainActivity"  
    android:configChanges="orientation|screenSize|keyboardHidden">  
</activity>  

// Step2: 在 Fragment 中响应配置变更  
override fun onConfigurationChanged(newConfig: Configuration) {  
    super.onConfigurationChanged(newConfig)  
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {  
        // 横屏布局调整  
        recyclerView.layoutManager = GridLayoutManager(context, 4)  
    } else {  
        // 竖屏布局调整  
        recyclerView.layoutManager = LinearLayoutManager(context)  
    }  
}

方案对比

自动重建 + 状态保存手动处理配置变更
优点代码简单,系统自动处理避免 UI 闪烁,提升性能
缺点会有短暂重建过程需适配所有可能的配置变更组合
建议默认使用(配合 ViewModel)仅用于性能敏感场景(如游戏、视频)

最佳实践

  1. 视图绑定:在 onCreateView() 中初始化视图,在 onDestroyView() 中释放
  2. 异步操作:在 onResume() 启动任务,在 onPause() 停止任务
  3. 内存管理:避免在 Fragment 中持有 Activity 的强引用
  4. 状态恢复:始终通过 savedInstanceState 处理配置变更
  5. 通信机制:使用 ViewModel + LiveData 替代直接与 Activity 交互

上次更新于: