先把结论放在前面:Fragment 生命周期有三套——自己的、视图的、上下文的,三套时机不同,答的时候必须分开说。最需要立住的一句话是「onCreateView 到 onDestroyView 之间,视图生命周期开始,Fragment 实例还在;onDestroy 之后视图才彻底消失」。围绕这条主线,配置变更行为、内存与线程约束都能自然挂上去。
Fragment 生命周期看起来简单,追问起来全是细节。这篇把它掰开揉碎,从概念到坑点过一遍。
先把三套生命周期画清楚
很多人答错,是因为把三套回调混成一条线。正确结构是这样的:
Fragment 实例创建
onAttach() → 拿到 Context / Activity
onCreate() → 数据初始化
onCreateView() → 创建视图树 ┐
onViewCreated() → 找控件、绑数据 │ 视图生命周期
onStart() → 可见 │
onResume() → 可交互 │
onPause() → 不可交互 │
onStop() → 不可见 │
onDestroyView() → 释放视图引用 ┘
onDestroy() → Fragment 实例销毁
onDetach() → 脱离 Context
关键分界:视图可以在 Fragment 还活着的时候被销毁。show/hide 不会走到 onDestroyView,但放回 Fragment 栈、replace 后长期不进栈、或配置变更时,视图会被销毁而实例可能保留(setRetainInstance 已废弃,现代做法靠 ViewModel 保留数据)。所以"视图生命周期"和"实例生命周期"是套在一起的两个环。
最常见的坑是
在 onViewCreated 用 this 注册 observe,返回时重复收集出现重复弹窗。
原因是 LiveData.observe 的 owner 传了 Fragment 自己(this),它活到 onDestroy;但视图生命周期只到 onDestroyView。放进返回栈的 Fragment 视图被销毁后再恢复,observer 还挂着,于是再点一次按钮触发两次回调,弹出两个对话框——而用户看到的是一个。
修法有两条,优先第一条:
class DetailFragment : Fragment(R.layout.fragment_detail) {
private val viewModel: DetailViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 正确:绑视图生命周期,视图没了自动解绑
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
state -> render(view, state) }
}
}
// LiveData 同理,owner 传 viewLifecycleOwner
viewModel.event.observe(viewLifecycleOwner) {
e -> handle(e) }
}
}
顺带把 view 别当字段存(private var rootView: View?)这个老写法一起戒掉。onDestroyView 之后 rootView 还指着已经脱离窗口的视图树,一旦被后续代码用到就崩。确实需要在视图存活期之外访问坐标或尺寸时,在 onDestroyView 里主动置 null。
还有一个更隐蔽的坑:Fragment 生命周期的实践经验是前提先声明,失败路径给兜底,事故挡在上线前。落到具体动作上有三条。① 用 FragmentTransaction.commit() 而不是 commitAllowingStateLoss()——后者能让状态丢失,属于掩盖问题;② 事务用 childFragmentManager 而不是 supportFragmentManager 给 DialogFragment 承载;③ 状态保存参数只在 onSaveInstanceState 里写进 savedInstanceState,不要存成 Fragment 字段,否则重建后读不到。
再把三套生命周期的观察点列清楚,因为这是实际写代码时的对照表。
| 要做的事 | 该挂在哪 |
|---|---|
| 找控件、设置监听 | onViewCreated |
| 加载数据、订阅数据流 | onViewCreated + viewLifecycleOwner + repeatOnLifecycle(STARTED) |
| 需要 Context/Activity 的初始化 | onAttach 之后(但不要在 onAttach 里做重活) |
| 跨配置的临时大对象 | 不挂 Fragment,改用 onRetainCustomNonConfigurationInstance 或 ViewModel |
| 注册需要注销的监听 | onStart 注册、onStop 注销(或用 repeatOnLifecycle 自动管理) |
| 耗时任务 | viewLifecycleOwner.lifecycleScope(页面级)/ ViewModel 的 suspend(跨页面) |
| 进程死亡后要恢复的少量状态 | onSaveInstanceState 的 Bundle |
这张表的价值在于它把"生命周期回调"从背诵变成查表。面试时不用背,把这个逻辑讲出来即可——因为表本身是现场推导的,面试官看到的是判断力。
再补一个容易出错的点:Fragment 的状态恢复有两个独立的 Bundle。onSaveInstanceState(savedInstanceState) 里的 Bundle 由系统为当前 Activity 与其下所有 Fragment 自动保存与回传;同时 Fragment 还可以自己 setArguments() 传参。区别在于——arguments 是"创建时确定、生命周期内不变"的输入,savedInstanceState 是"运行中会变、需要恢复"的输出。把筛选条件写进 arguments 之后想改它,改不动,必须走 savedInstanceState 或共享 ViewModel。
三条高频追问
问:onHiddenChanged 和 onPause 的区别?
onPause 是生命周期回调,onHiddenChanged 是 show/hide 引起的可见性回调。add + hide 切换时 Fragment 不会走 onPause,只走 onHiddenChanged;而 replace 或进返回栈会同时触发两者。所以依赖"不可见就停流量"的需求必须用 Lifecycle + repeatOnLifecycle(STARTED),不能只判断 onHiddenChanged——这是很多"视频还在播"的 bug 来源。
问:为什么 setRetainInstance 被废弃了?
它原本靠 NonConfigurationInstances 跨配置变更保留 Fragment 实例,问题是内存泄漏难以排查、View 引用容易残留、也不利于进程死亡后的恢复。现在的官方路径是:ViewModel 保留数据(配置变更存活)、SavedStateHandle 恢复少量状态、视图每次重建。这个演进本身就是一道很好的追问点,能体现是否跟过版本变化。
问:Fragment 里能不能开线程做耗时操作?
能,但结论要说清"该放哪"。碎片化的短任务可以用 viewLifecycleOwner.lifecycleScope 启协程,范围绑定视图;跨页面的批量工作应该放到 applicationScope 或交给 ViewModel 里的 suspend 函数;需要进程死亡后继续的交给 WorkManager。绝不要在 Fragment 里持有裸线程——视图销毁后回调没人管,泄漏与崩溃都是迟早的事。
落到项目里怎么做
一条可直接落地的规则:任何视图相关的引用与订阅,owner 一律用 viewLifecycleOwner;任何数据的持有,放 ViewModel。这两条能挡掉九成 Fragment 问题。
配套的工程实践:给 Fragment 写一个基类,把 observe 的辅助方法收进去并强制传 viewLifecycleOwner,从代码层面消灭误用;列表页统一用 commit { replace(...) } 的 ktx 写法,拿到 FragmentManager 的执行结果;崩溃统计里把 NullPointerException 与 Fragment 相关的堆栈单独聚合,这类问题修一次就长期消失。
给正在准备面试的你
Fragment 生命周期的题,答法比知识本身更重要。按这条线组织答案:先分三套生命周期(实例 / 视图 / 上下文)→ 再说配置变更时哪些保留、哪些不保留(ViewModel 留、Fragment 的字段不留、视图重建)→ 最后落工具(viewLifecycleOwner、repeatOnLifecycle、不用 setRetainInstance)。顺手把 onHiddenChanged 与 onPause 的差异、commit 与 commitAllowingStateLoss 的取舍补上,整道题就答完了。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。
「Android软件开发面试·从入门到精通」连载系列
上一篇:状态保存与恢复:onSaveInstanceState-的时机
下一篇预告:Fragment-通信演进:从接口回调到共享-ViewModel
有任何问题欢迎在评论区留言交流。