21、Fragment:Fragment生命周期、FragmentManager、DialogFragment与BottomSheetDialog
Fragment,这个从Android早期就存在的组件,说实话,很多人对它又爱又恨。爱的是它让界面复用变得简单,恨的是它的生命周期实在太绕了。我在项目中见过不少因为Fragment生命周期没处理好导致的崩溃,比如空指针、状态丢失、界面闪烁……嗯,今天我们就把它彻底讲清楚。
Fragment的生命周期:比Activity更复杂的存在
Fragment的生命周期方法比Activity多得多。为什么?因为它要依附于Activity,还要考虑被添加、移除、替换、回退等各种场景。我个人习惯把Fragment的生命周期分成三个阶段来看:
- 创建阶段:onAttach → onCreate → onCreateView → onViewCreated → onActivityCreated → onStart → onResume
- 运行阶段:onResume ↔ onPause(可见性变化)
- 销毁阶段:onPause → onStop → onDestroyView → onDestroy → onDetach
核心要点:onCreateView和onDestroyView是成对出现的。每次Fragment的视图被创建或销毁时都会调用它们。这意味着如果你的Fragment被添加到回退栈然后又回来,onCreateView会再次执行。
我在项目中遇到过一个问题:在onCreateView里初始化了一个列表适配器,结果Fragment从回退栈回来时,适配器里的数据全丢了。为什么?因为onCreateView重新执行了,但之前的数据没有恢复。后来我学乖了,把数据初始化放在onCreate里,视图相关放在onCreateView里。
我的建议:把数据加载和状态恢复放在onCreate中,把视图绑定放在onViewCreated中。onCreateView只做布局填充,别干别的。
来看一个标准的Fragment生命周期代码示例:
class MyFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
// Fragment与Activity关联,context可用
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 数据初始化,状态恢复
// 注意:此时视图还没创建
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 只做布局填充
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 视图已创建,可以绑定控件、设置监听
// 我个人习惯把findViewById和点击事件写在这里
}
override fun onDestroyView() {
super.onDestroyView()
// 视图即将销毁,释放视图相关资源
// 比如取消图片加载、清空引用等
}
override fun onDetach() {
super.onDetach()
// Fragment与Activity解绑
}
}
注意:onDestroyView之后,Fragment的视图被销毁,但Fragment实例还在。如果你在onDestroyView之后还持有视图引用,就会造成内存泄漏。我曾经因为这个被OOM坑过好几次。
FragmentManager:Fragment的管家
FragmentManager负责管理Fragment的添加、移除、替换、回退等操作。说白了,它就是Fragment的大管家。每个Activity或Fragment都有自己的FragmentManager。
你想想看,如果不用FragmentManager,你怎么把Fragment显示到屏幕上?手动new一个然后setContentView?显然不行。FragmentManager帮我们处理了生命周期、事务提交、状态保存等复杂逻辑。
常用的操作有这些:
- add():添加Fragment,不会移除已有的
- replace():替换当前Fragment,会移除已有的
- remove():移除Fragment
- hide()/show():隐藏/显示Fragment,不销毁视图
- addToBackStack():添加到回退栈
来看一个典型的事务提交:
supportFragmentManager.commit {
replace(R.id.fragment_container, MyFragment())
addToBackStack(null) // 添加到回退栈
}
这里有个坑:commit是异步的。它会在主线程的下一个空闲时机执行。如果你需要立即执行,可以用commitNow()。但要注意,commitNow()不支持回退栈。
避坑指南:我曾经在onSaveInstanceState之后提交事务,结果抛出了IllegalStateException。因为状态已经保存,不能再提交事务。解决方案是在onCreate中恢复状态时再提交,或者使用commitAllowingStateLoss()——但这个方法要慎用,因为它会丢失状态。
DialogFragment:比Dialog更优雅的选择
很多人还在用AlertDialog,其实DialogFragment才是官方推荐的方式。为什么?因为它有自己的生命周期,能正确处理屏幕旋转和状态恢复。说白了,DialogFragment就是一个以对话框形式展示的Fragment。
DialogFragment的生命周期和普通Fragment基本一致,但多了几个方法:
- onCreateDialog():创建对话框,可以返回自定义的Dialog
- setupDialog():设置对话框的样式和属性
- dismiss():关闭对话框
来看一个简单的DialogFragment示例:
class ConfirmDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireContext())
.setTitle("确认删除")
.setMessage("确定要删除这条记录吗?")
.setPositiveButton("确定") { _, _ ->
// 执行删除操作
}
.setNegativeButton("取消", null)
.create()
}
}
// 使用
ConfirmDialogFragment().show(supportFragmentManager, "confirm_dialog")
关键点:DialogFragment的show()方法接收一个FragmentManager和一个tag。tag用于在屏幕旋转后恢复对话框。如果你不传tag,旋转后对话框会消失。
我在项目中遇到过一个问题:DialogFragment显示后,点击返回键直接关闭了整个Activity。为什么?因为我没有处理返回键事件。解决方案是在onCreateDialog中设置:
dialog?.setCanceledOnTouchOutside(true)
dialog?.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
dismiss()
true
} else false
}
BottomSheetDialog:底部弹出的优雅方案
BottomSheetDialog是DialogFragment的一个子类,专门用于实现底部弹出的对话框。它继承自AppCompatDialogFragment,所以也拥有完整的生命周期管理。
使用起来很简单:
class MyBottomSheetDialog : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 绑定控件,设置数据
}
}
// 使用
MyBottomSheetDialog().show(supportFragmentManager, "bottom_sheet")
BottomSheetDialog有几个重要的特性:
- 可拖拽:用户可以通过手势上下拖动
- 两种状态:展开和折叠
- 可设置高度:通过设置peekHeight控制折叠时的高度
- 可禁用拖拽:通过设置isDraggable为false
注意:BottomSheetDialog默认是半透明的,背景会变暗。如果你不想让背景变暗,可以在onCreate中设置:setStyle(STYLE_NO_TITLE, R.style.Theme_Design_BottomSheetDialog)。另外,如果内容太少,它可能不会完全展开,需要设置behavior的skipCollapsed为true。
我曾经在项目中用BottomSheetDialog做了一个分享面板,效果非常好。但有个坑:如果Fragment的宿主Activity被销毁重建,BottomSheetDialog的状态不会自动恢复。解决方案是在onSaveInstanceState中保存状态,然后在onCreate中恢复。
知识体系总览
下面这张图总结了Fragment相关的核心知识点,包括生命周期、FragmentManager、DialogFragment和BottomSheetDialog的关系:
总结一下:Fragment的生命周期比Activity复杂,但掌握了规律就不难。FragmentManager是操作Fragment的核心工具,事务提交要注意异步和状态保存。DialogFragment和BottomSheetDialog是官方推荐的对话框方案,比传统Dialog更健壮。嗯,这些知识点在实际项目中几乎天天用到,希望你能真正理解它们。
公众号:蓝海资料掘金营,微信deep3321