简介
Android 的 Fragment
是一个具有自己生命周期的 可重用 UI 组件,能够在运行时灵活地添加、移除和替换,从而支持单 Activity 多界面、动态布局和响应式设计。掌握 Fragment 的生命周期有助于正确地在各个阶段执行初始化、资源绑定、状态保存与释放操作,避免内存泄漏和 UI 崩溃。
1. Fragment 生命周期核心方法
Fragment 的生命周期与 Activity 紧密关联,但包含更多与视图相关的回调:
生命周期方法 | 触发时机 | 用途 |
---|---|---|
onAttach() | Fragment 与 Activity 关联时 | 获取 Activity 引用,初始化参数 |
onCreate() | Fragment 首次创建时(早于视图创建) | 初始化非视图数据(如数据库查询) |
onCreateView() | 创建 Fragment 的视图时 | 加载布局文件(返回 View 对象) |
onViewCreated() | 在 onCreateView() 执行完成后 | 获取视图控件引用、配置 RecyclerView 等 |
onActivityCreated() | 关联的 Activity 已完成 onCreate() | 确保 Activity 视图就绪,执行 Activity 与 Fragment 的交互逻辑 |
onStart() | Fragment 可见时(与 Activity 同步) | 更新 UI 数据 |
onResume() | Fragment 可交互时(与 Activity 同步) | 启动动画、注册传感器监听 |
onPause() | Fragment 失去焦点时(如跳转其他 Activity) | 停止耗时操作、保存临时数据 |
onStop() | Fragment 不可见时 | 释放 UI 相关资源 |
onDestroyView() | Fragment 视图被移除时(但 Fragment 实例仍存在) | 清理视图绑定、取消异步任务 |
onDestroy() | Fragment 即将被销毁时 | 释放非视图资源 |
onDetach() | Fragment 与 Activity 解除关联时 | 清除 Activity 引用 |
2. Fragment 生命周期状态图
3. Fragment 生命周期示例
- app
- src/main
- java/com/example/fragmentdemo
- MainActivity
- BaseFragment
- FragmentA
- FragmentB
- res/layout
- activity_main.xml
- fragment_a.xml
- fragment_b.xml
-
BaseFragment
abstract class BaseFragment : Fragment() { protected val TAG: String = "FragmentLifecycle" override fun onAttach(@NonNull context: Context) { super.onAttach(context) Log.d(TAG, "${this::class.java.simpleName} onAttach") } override fun onCreate(@Nullable savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d(TAG, "${this::class.java.simpleName} onCreate") } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { Log.d(TAG, "${this::class.java.simpleName} onCreateView") return inflater.inflate(getLayoutId(), container, false) } override fun onViewCreated(@NonNull view: View, @Nullable savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Log.d(TAG, "${this::class.java.simpleName} onViewCreated") } override fun onActivityCreated(@Nullable savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) Log.d(TAG, "${this::class.java.simpleName} onActivityCreated") } override fun onStart() { super.onStart() Log.d(TAG, "${this::class.java.simpleName} onStart") } override fun onResume() { super.onResume() Log.d(TAG, "${this::class.java.simpleName} onResume") } override fun onPause() { super.onPause() Log.d(TAG, "${this::class.java.simpleName} onPause") } override fun onStop() { super.onStop() Log.d(TAG, "${this::class.java.simpleName} onStop") } override fun onDestroyView() { super.onDestroyView() Log.d(TAG, "${this::class.java.simpleName} onDestroyView") } override fun onDestroy() { super.onDestroy() Log.d(TAG, "${this::class.java.simpleName} onDestroy") } override fun onDetach() { super.onDetach() Log.d(TAG, "${this::class.java.simpleName} onDetach") } /** 子类必须提供此方法来返回布局资源 ID */ @LayoutRes protected abstract fun getLayoutId(): Int }
-
fragment_a.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".FragmentA"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="25sp" android:textColor="@color/black" android:text="This is Fragment A" /> </FrameLayout>
-
fragment_b.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".FragmentB"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="25sp" android:textColor="@color/black" android:text="This is Fragment B" />
-
FragmentA
class FragmentA : BaseFragment() { override fun getLayoutId() = R.layout.fragment_a }
-
FragmentB
class FragmentB : BaseFragment() { override fun getLayoutId() = R.layout.fragment_b }
-
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <!-- 切换 Fragment 的按钮 --> <Button android:id="@+id/btn_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="切换到 FragmentB" android:onClick="switchToFragmentB" /> <!-- Fragment 容器 --> <FrameLayout android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
-
MainActivity
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 首次添加 FragmentA supportFragmentManager.beginTransaction() .add(R.id.fragment_container, FragmentA()) .commit() } fun switchToFragmentB(view: View) { supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, FragmentB()) .commit() } }
-
初始加载
FragmentA
的生命周期如下FragmentA onAttach FragmentA onCreate FragmentA onCreateView FragmentA onViewCreated FragmentA onActivityCreated FragmentA onStart FragmentA onResume
-
切换
FragmentB
不加入返回栈的生命周期如下FragmentA onPause FragmentA onStop FragmentB onAttach FragmentB onCreate FragmentB onCreateView FragmentB onViewCreated FragmentB onActivityCreated FragmentB onStart FragmentA onDestroyView FragmentA onDestroy FragmentA onDetach FragmentB onResume
-
切换
FragmentB
加入返回栈的生命周期如下fun switchToFragmentB(view: View) { supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, FragmentB()) .addToBackStack("fragmentB") // 可选:加入返回栈 .commit() }
// 点击切换按钮 FragmentA onPause FragmentA onStop FragmentA onDestroyView FragmentB onAttach FragmentB onCreate FragmentB onCreateView FragmentB onViewCreated FragmentB onActivityCreated FragmentB onStart FragmentB onResume // 按返回键返回 FragmentA FragmentB onPause FragmentB onStop FragmentB onDestroyView FragmentB onDestroy FragmentB onDetach // FragmentA 重新创建视图 FragmentA onCreateView FragmentA onViewCreated FragmentA onActivityCreated FragmentA onStart FragmentA onResume
-
4. 示例生命周期流程图
启动 Activity
└── 添加 FragmentA
├── onAttach
├── onCreate
├── onCreateView
├── onViewCreated
├── onActivityCreated
├── onStart
└── onResume
替换为 FragmentB(无返回栈)
├── FragmentA.onPause
├── FragmentA.onStop
├── FragmentA.onDestroyView
├── FragmentA.onDestroy
├── FragmentA.onDetach
└── FragmentB 完整生命周期
替换为 FragmentB(有返回栈)
├── FragmentA.onPause
├── FragmentA.onStop
├── FragmentA.onDestroyView
└── FragmentB 完整生命周期(除 onDestroy/onDetach)
按返回键
├── FragmentB.onPause
├── FragmentB.onStop
├── FragmentB.onDestroyView
├── FragmentB.onDestroy
├── FragmentB.onDetach
└── FragmentA 重建视图
├── onCreateView
├── onViewCreated
├── onActivityCreated
├── onStart
└── onResume
5. 关键结论
- 视图生命周期
onCreateView
和onDestroyView
控制视图的创建与销毁。- 使用
addToBackStack
后,Fragment 实例保留,但视图会被销毁。
- 状态保留
- 在
onSaveInstanceState()
保存数据(在onCreate
中恢复)。 - 视图相关数据应在
onDestroyView
中清理。
- 在
- 最佳实践
- 初始化数据:在
onCreate
(非视图数据)或onViewCreated
(视图相关)。 - 释放资源:
- 视图绑定在
onDestroyView
中置空。 - 后台线程在
onStop
中取消。
- 视图绑定在
- 避免内存泄漏:在
onDetach
中清除 Activity 引用
- 初始化数据:在
6. 常见 Fragment 面试
-
什么是 Fragment?它与 Activity 有何区别?
-
Fragment 是 Android Support Library(AndroidX)提供的可重用 UI 组件,具有自己独立的生命周期,但必须托管在
Activity
中。 -
区别:
Activity
代表一个完整的屏幕,必须在AndroidManifest.xml
中声明;而Fragment
只是屏幕的一部分,可以在运行时动态增删,不需在清单里注册,并支持多个 Fragment 并列显示(如平板双页布局) 。
-
-
详细描述 Fragment 的生命周期以及每个回调的作用
-
Fragment 有独立的生命周期回调,与宿主 Activity 生命周期紧密关联,其核心顺序为:
onAttach() → onCreate() → onCreateView() → onViewCreated() → onStart() → onResume() ▲ ▼ onPause() ← onStop() ← onDestroyView() ← onDestroy() ← onDetach()
-
onAttach(Context):Fragment 与 Activity 关联时调用,通常获取
Context
或接口回调引用。 -
onCreate(Bundle?):进行全局变量或非视图逻辑初始化,可以调用
setRetainInstance(true)
保留 Fragment 实例。 -
onCreateView(…):创建并返回 UI 布局,执行
inflater.inflate(...)
。 -
onViewCreated(View, Bundle?):视图创建完成后调用,安全绑定子视图和注册 LiveData 观察者 。
-
onStart()/onResume():Fragment 可见并获得焦点,恢复动画或摄像头预览等交互逻辑。
-
onPause()/onStop():停止动画、保存易丢失状态,释放重量级资源(如传感器、广播接收器)。
-
onDestroyView():销毁视图层次,清理与视图绑定的引用,防止内存泄漏。
-
onDestroy()/onDetach():彻底释放后台资源,并与 Activity 分离 。
-
-
-
Fragment 与 Activity 之间如何传递数据?
-
通过
setArguments()
/getArguments()
:在创建 Fragment 实例前,调用fragment.arguments = Bundle().apply { putString("key", value) }
,在onCreate()
中读取。此方法保证在重建时数据不会丢失。 -
宿主 Activity 直接调用公共方法:Activity 在
fragmentManager.findFragmentById()
后,通过类型转换调用 Fragment 的公有方法传递。 -
共享 ViewModel(推荐,Jetpack):Activity 和 Fragment 共享同一个
ViewModel
,通过 LiveData 进行双向通信,无需显式管理 Lifecycle。 -
ragment Result API(AndroidX 1.3+):使用
setFragmentResult()
/setFragmentResultListener()
在父 Fragment 或 Activity 间传递数据,更加解耦。
-
-
如何在 Fragment 事务中使用回退栈(Back Stack),以及
add()
与replace()
的区别?- addToBackStack(tag):在调用
.beginTransaction().add(...).addToBackStack(tag).commit()
后,当前事务会被加入回退栈,用户按返回键时可撤销该事务。 - add():将新 Fragment 覆盖在容器上,但不移除旧 Fragment,可实现多个重叠效果,需手动 hide/show 来管理可见性 。
- replace():先执行
remove()
再add()
,移除容器内所有旧 Fragment,然后添加新 Fragment,常用于纯粹替换场景 。 - 回退行为:
add()
+addToBackStack()
:回退时会 remove 新 Fragment 并 show 旧 Fragment。replace()
+addToBackStack()
:回退时 remove 替换的 Fragment,并重新 add 之前的 Fragment 实例 。
- addToBackStack(tag):在调用
-
Fragment 状态保存与
setRetainInstance(true)
的作用-
onSaveInstanceState(Bundle):当 Fragment 被销毁(如配置变化)前,系统会回调此方法。开发者应在其中保存 UI 状态(如滚动位置、输入内容)到
Bundle
。 -
setRetainInstance(true)
:设置后,在父 Activity 重建(如旋转)时,Fragment 实例不会被销毁,保留成员变量。但仍会销毁/重建视图层次;仅适用于保存非视图状态且慎用,避免与 ViewBinding 冲突。
-
-
嵌套 Fragment(Child Fragment)与
getChildFragmentManager()
的使用场景-
嵌套 Fragment:在一个 Fragment 内再承载多个子 Fragment,用于实现如选项卡、动态表单多级结构等复杂 UI。
-
使用
childFragmentManager
(而非parentFragmentManager
)进行事务管理,确保子 Fragment 生命周期与父 Fragment 关联,并自动在父销毁时清理子 Fragment。
-
-
Fragment 性能优化与常见坑
- 避免过度嵌套:深度嵌套会增加测量与布局开销,建议扁平化布局或使用
ConstraintLayout
。 - 使用 View Binding / Data Binding:减少
findViewById
,在onDestroyView()
中将绑定置空避免泄漏。 - 合理使用事务方式:大量 add/remove/replace 可能产生视图碎片化,考虑使用
show()
/hide()
配合复用可提高效率 。 - 异步加载:在
onCreateView()
只做视图膨胀,耗时操作(网络、数据库)放到onViewCreated()
后的协程或 RxJava 中处理。 - 测试:利用
FragmentScenario
在隔离环境下测试生命周期与 UI 交互,保证稳定性。
- 避免过度嵌套:深度嵌套会增加测量与布局开销,建议扁平化布局或使用