简介
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 交互,保证稳定性。
- 避免过度嵌套:深度嵌套会增加测量与布局开销,建议扁平化布局或使用