第124篇 Fragment 生命周期:与 Activity 的联动陷阱

简介: Fragment生命周期含三套独立流程:实例、视图、上下文。关键分界是`onCreateView`至`onDestroyView`为视图生命周期,此时Fragment实例仍存活;`onDestroy`后实例才真正销毁。常见坑如重复弹窗、内存泄漏,根源在于混淆三者时机。推荐实践:视图相关操作绑定`viewLifecycleOwner`,数据交由`ViewModel`管理。

先把结论放在前面: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

有任何问题欢迎在评论区留言交流。

相关文章
|
API Android开发 开发者
第127篇 ViewPager2 与 Fragment:懒加载的正确实现
ViewPager2 内核是 RecyclerView,由 FragmentStateAdapter 托管 Fragment 生命周期,依赖 `getItemId`/`containsItem` 实现稳定复用。旧版 `setUserVisibleHint` 已废弃,应统一用 `repeatOnLifecycle(STARTED)` 控制可见性逻辑。关键在理解“复用机制+生命周期托管”,避免数据错乱、重复加载与内存泄漏。
96 0
|
存储 安全 API
第130篇 WorkManager:后台任务的最终归宿
WorkManager 专解“保证完成+尊重系统调度”问题,非实时执行方案。核心能力:持久化任务队列、约束条件(如网络/电量/存储)、可组合工作链。选型关键:可延迟但必须成功——用它;需即时响应、用户可见——用前台服务或推送。
90 0
|
23天前
|
调度 Android开发 容器
第126篇 Fragment 事务与回退栈:add、replace、show、hide
Fragment事务异步队列执行,非调用即生效;回退栈存操作记录而非实例。`show/hide` 快但常驻内存、不可回退;`replace`+栈可回退但重建视图。选型关键:是否需回退?是否敏感内存?——是判断题,非知识点。
76 1
|
缓存 API Android开发
第125篇 Fragment 通信演进:从接口回调到共享 ViewModel
本文梳理Fragment通信演进史,揭示五代方案(接口回调、setFragmentResult、广播、共享ViewModel、Fragment Result API)的耦合本质。核心结论:**仅推荐三种场景化方案——Fragment Result API传一次性结果、共享ViewModel管共同状态、事件总线限于跨模块解耦**。重点剖析“耦合藏在哪”,并指出`viewLifecycleOwner`误用、事件重复消费、ViewModel状态污染等高频坑及自动化防治策略。
63 0
|
缓存 API Android开发
第129篇 前台服务与后台限制:通知与省电的平衡
前台服务本质是“用户可见即获优待”的系统契约,非绝对保活。需严格满足:Android 8+ 5秒内调`startForeground`、按API版本声明`foregroundServiceType`及对应权限、展示可感知通知。违背即崩溃或被拒。
117 0
|
消息中间件 监控 Java
第144篇 Handler 消息机制:Looper、MessageQueue 与 ThreadLocal
Handler消息机制核心在于四要素:Looper循环取、MessageQueue按时间排序、Message对象池复用、dispatchMessage分发回调。关键前提为Handler与Looper绑定,而Looper通过ThreadLocal与线程一对一绑定——这是理解子线程需prepare、主线程可直用等行为的根基。机制本质是工程细节,非高深API。
91 0
|
JSON API 数据库
第131篇 BroadcastReceiver:静态注册与动态注册的存亡
本文深入剖析 BroadcastReceiver 的三层认知:API 层(能收广播)、机制层(知限制规则)、工程层(懂替代方案)。详解静态/动态注册、`goAsync()` 与 ANR 避坑、隐式广播限制(Android 8+)、有序广播淘汰原因及 `WorkManager` 等现代替代方案,助你真正掌握广播本质。
80 0
|
Java 测试技术 API
第132篇 ContentProvider:跨进程数据共享的标准入口
ContentProvider 是Android四大组件中唯一非UI组件,本质是跨进程的表结构接口+数据变更通知机制。其onCreate早于Application.onCreate,且在所有访问进程里执行,易拖慢冷启动;query走Binder事务,需子线程调用并分页防溢出。核心原则:保持极轻,重初始化移交Application。
100 0
|
23天前
|
安全 Java 定位技术
第117篇 Kotlin 阶段面试通关地图:高频题怎么连成网
这是Kotlin面试的结构化复习地图,非知识点罗列,而是将60+篇内容按「六大知识块×三层深度」(会用/懂机制/能取舍)分层归因,直击面试区分度核心。助你快速定位保底分与拉分点,掌握高频题答题骨架、交叉跳转逻辑及三大避坑指南,真正把知识织成可检索、可推演、可落地的活网络。
77 1
|
23天前
|
传感器 API Android开发
第121篇 Activity 生命周期全解:七个回调的成对关系
本文深入解析Activity生命周期的两大维度:视图生命周期(6个回调)与进程生命周期(状态保存/恢复),直击面试高频考点——“为何如此设计”及“进程被杀后如何恢复”。厘清`onSaveInstanceState`触发条件、Bundle大小限制、ViewModel与状态保存的边界,并给出工程最佳实践与避坑指南。
80 1

热门文章

最新文章