
Fragment 是 Android 开发里绕不开的话题从早期的支持库一路走到现在的 Jetpack Fragment它已经不只是“块 UI 那么简单了。很多人用 Fragment 用得很熟但一谈到实现原理就有点懵为什么 Fragment 会有生命周期它和 Activity 之间到底怎么通信事务提交后发生了什么状态又是怎么保存和恢复的这篇文章不打算从头到尾复述官方文档而是从源码和实际架构的角度把 Fragment 实现原理里几个最关键的部分拆开讲透顺便把我在项目里踩过的一些坑也一并带上。适合已经写过一段时间 Fragment、想深入理解内部机制、或者在排查 Fragment 相关疑难 bug 的同学阅读。我一直觉得理解一个框架的运行机制比背一堆接口用法要重要得多。因为只有理解了背后的设计和约束你才能在该用 Fragment 的地方用对在它出问题的时候快速定位而不是靠网上搜到的各种“玄学解法”去试。1. Fragment 的整体设计为什么它会存在又解决了什么问题1.1 Fragment 不是 View而是被托管的小型控制器很多人一开始学 Fragment 时容易把它当成一种 View 容器就觉得 Fragment 就是往界面上贴一块东西。这个认知其实从一开始就跑偏了。Fragment 从设计上更接近一个“轻量级的 Activity”它是一个挂在 Activity 内部的控制器对象持有自己的布局、自己的生命周期、自己的状态保存能力但它没有 Window也没有独立的返回事件处理机制这些还是依赖宿主 Activity。这也是 Fragment 被设计出来的最初动机在大屏设备上我们希望同一个界面逻辑能在不同布局模式下复用。比如平板横屏时左边是列表、右边是详情竖屏时则变成两个独立的页面这种动态适配如果只靠 Activity 是做不到的。所以 Fragment 的本质是一个状态机它的状态变化由 FragmentManager 来驱动。FragmentManager 像是 Fragment 的管家负责创建 Fragment、恢复 Fragment、分发生命周期事件、处理事务、维护回退栈。这里有一个值得注意的点Fragment 的视图View可以被销毁但 Fragment 的实例和状态仍然可以保留。这意味着 Fragment 的 onDestroyView 之后它持有的 mView 会置空但 Fragment 对象本身还活着还在 FragmentManager 的 mActive 集合里待着。这是理解 Fragment 生命周期和状态恢复的重要基础。1.2 一个宿主多个 Fragment 的调度模型一个 Activity 里可能有多个 Fragment 同时存在。比如一个典型的主页界面底部有 Tab每个 Tab 管理自己的 Fragment有些界面还会做 Fragment 的嵌套。这种情况下宿主 Activity 只需要通过 FragmentManager 来操作这些 Fragment而不需要关心它们的内部实现。从源码角度看FragmentManager 是所有 Fragment 的中枢。它维持了几个关键的数据结构mBackStack回退栈里面存的是事务记录 Op 的列表mActive当前还活跃的 Fragment 列表包括 view 被销毁但实例还在的mAdded当前已经添加到 Activity 上的 Fragment 列表mCreated、mPostponed 等状态集合用于管理 Fragment 生命周期事件的分发每次事务提交最终都会走到一个 moveToState 的方法里FragmentManager 会根据目标状态把 Fragment 的状态一点点推过去。Fragment 状态一共有八个层级INITIALIZING、ATTACHED、CREATED、VIEW_CREATED、AWAITING_EXIT_EFFECTS、ACTIVITY_CREATED、STARTED、RESUMED。每次 moveToState 都会触发对应的回调比如从 ATTACHED 到 CREATED会调用 onCreate从 CREATED 到 VIEW_CREATED会调用 onCreateView。这些状态之间的升降不是一次跳到的而是一步一步走的。这样设计的好处是任何一步出错你都能知道卡在哪也方便 FragmentManager 统一调度。2. Fragment 生命周期背后的状态机2.1 状态值是怎么定义的Fragment 的状态定义在源码里其实是一组常量不同版本有细微差别但大致维持这样一个递增关系INITIALIZING 状态是 Fragment 刚被实例化时还没有进入生命周期体系。ATTACHED 时已经和宿主关联能通过 getActivity 拿到宿主。CREATED 时已经完成了构造和属性恢复但视图可能还没有创建。到了 VIEW_CREATED说明 onCreateView 的返回值已经赋值给 mView 了但可能因为动画等原因还处在一个中间态。ACTIVITY_CREATED 说明宿主 Activity 的 onCreate 已经走完Fragment 可以安全访问 Activity 内的视图了。STARTED 和 RESUMED 则代表 Fragment 完全对用户可见、可交互。这个状态机最妙的一点Fragment 的每个生命周期方法基本都能映射到一次状态迁转。比如 onPause 对应从 RESUMED 回退到 STARTEDonStop 对应从 STARTED 回退到 ACTIVITY_CREATED早期版本是 CREATED。你在生命周期回调里做的事本质上就是在响应状态变化。2.2 状态上移与下移每个 Fragment 都逃不过这两条路状态上移比如从 INITIALIZING 到 RESUMED时FragmentManager 会依次调用 onAttach、onCreate、onCreateView、onViewCreated、onActivityCreated、onStart、onResume。状态下移时则反过来从 onPause、onStop、onDestroyView、onDestroy、onDetach 一路下来。这里面有个很多开发者忽视的细节Fragment 的 onDestroyView 和 onDestroy 是分开的。onDestroyView 只销毁视图但 Fragment 还活着还在 FragmentManager 的 mActive 里待着。onDestroy 才是真正销毁 Fragment。这也就是为什么官方建议在 onDestroyView 里把对视图的引用置空避免内存泄漏。下移的时候FragmentManager 还会做一个特殊操作如果 Fragment 的状态一路回退到 CREATED 以下它会清除 mSavedViewState、mView 等视图相关数据但 Fragment 的实例和参数arguments仍然保留。这样下次再进入时可以快速从 mActive 里找到这个 Fragment而不是重新 new 一个。我在项目里就踩过这样一个坑在 Fragment 的 onStop 里保存了一个 View 的引用想着恢复界面时直接复用结果从后台回到前台发现 View 已经 detach 了界面崩了。后来才明白onStop 之后 View 有可能已经被销毁不能假设视图还一直存在。正确做法是保存数据而不是保存 View 引用。2.3 宿主生命周期如何映射到 Fragment 生命周期Fragment 的宿主是 Activity但 Fragment 并不是直接监听 Activity 的生命周期回调而是由 FragmentManager 在 Activity 的生命周期方法里主动分发。以 ComponentActivity 为例它在 onCreate 里会调用 mFragmentLifecycleRegistry 相关的逻辑间接把生命周期事件传给 FragmentManager再由 FragmentManager 去推进各个 Fragment 的状态。这种设计带来的一个结果是Fragment 的生命周期永远滞后于 Activity 一点点。比如 Activity 已经走完 onStartFragment 的 onStart 才会被调用。你如果在 Activity 的 onCreate 里去 getFragmentManager 找 Fragment能找到实例但此时 Fragment 可能还处于 CREATED 状态。理解这个映射关系对于排查生命周期相关的诡异问题非常有帮助。比如在 Fragment 的 onActivityCreated 里试图访问 Activity 的视图这时候基本是安全的因为 Activity 的 onCreate 已经结束了。但在 onViewCreated 里访问 Activity 的某些资源时就要格外小心因为 Fragment 的视图创建可能比 Activity 的某些初始化更早发生。3. Fragment 事务机制与状态管理的核心3.1 事务为什么要“先记录再执行”使用 Fragment 的人一定写过类似下面的代码getSupportFragmentManager().beginTransaction() .replace(R.id.container, new ExampleFragment(), tag) .addToBackStack(null) .commit();这里面有个容易被忽略的细节commit() 并不是立即执行事务内容而是先把事务记录进队列再在某个时机异步执行。为什么要异步执行因为 FragmentManager 的状态管理是统一调度的如果在 Activity 的任何生命周期阶段都能立刻执行事务很容易出现状态不一致。比如你在一个 Fragment 的 onPause 里又去提交了一个事务如果立即执行FragmentManager 的状态机可能正处于 RECOMMENDED 阶段再强行推进状态就会导致其他 Fragment 的生命周期错乱。源码里有一个 ArrayList 的 mPendingActions所有 commit 的事务都会被包装成 OpGenerator 加进这个队列等 FragmentManager 执行到 execPendingActions 时再统一处理。execPendingActions 的调用时机包括主线程消息循环空闲时、Activity 生命周期事件发生时、以及外部主动调用 executePendingTransactions() 时。所以 commit() 之后别指望事务马上生效如果你真的需要立即生效得用 commitNow()。但 commitNow 和 commit 还有一个重要区别commitNow 不支持 addToBackStack如果要加入回退栈必须用 commit。3.2 事务操作符的底层表示一个事务里可以有 add、remove、replace、hide、show、detach、attach 这些操作。这些操作在源码里都被封装成了 Op 对象每个 Op 保存了操作类型、涉及的 Fragment、以及一些附加参数。一个事务对应一个 BackStackRecord它内部有一个 ArrayList 。当 FragmentManager 执行事务时会遍历这些 Op逐个调用对应的方法。比如 add 操作会调用 addFragmentremove 操作会调用 removeFragment。这里有一个很重要的细节replace 本质上不是独立操作而是先 remove 掉所有同容器的已有 Fragment再 add 新的 Fragment。用 replace 时FragmentManager 会给被替换的 Fragment 创建退出动画新 Fragment 创建进入动画。动画执行过程中新旧 Fragment 可能同时处于 RESUMED 状态如果动画时长较长这就是为什么你在做转场动画时偶尔能看到两个 Fragment 视图短暂重叠。解决这个问题的思路一般是调整动画时长或者改用 detach 和 attach 的组合操作。3.3 回退栈到底是怎么工作的addToBackStack 这个功能很多人在用但很少去想它内部的机制。当你调用 addToBackStack(null) 后这个 BackStackRecord 不会在事务执行完就丢弃而是被 push 到 FragmentManager 的 mBackStack 里。当用户按下返回键时Activity 会调用 FragmentManager 的 popBackStackImmediate() 或 popBackStack()此时 FragmentManager 会从 mBackStack 里取出栈顶的事务反向执行里面的所有 Op。反向执行的意思是原来 add 的就变成 remove原来 remove 的就变成 add原来是 hide 的就变成 show。这样就能把界面状态还原到事务提交之前。回退栈还有几个容易踩坑的点回退栈里的记录不是 Fragment而是事务记录。如果你连续提交了多个事务回退栈里会有多条记录。用 popBackStack(String tag, 0) 只能弹出标记为 tag 的那条记录之上的事务而不是只弹那一条。回退栈支持 popBackStack 到某个指定记录也支持用 POP_BACK_STACK_INCLUSIVE 标志把该记录一起弹出。commit 后紧接着 popBackStack 不会立即执行需要在 Activity 空闲时才会执行所以有时候代码里写的 popBackStack 效果不明显其实是时序问题。我实际项目中曾经遇到一个 bug连续切换 Tab 时Fragment 越来越多内存持续攀升。后来排查发现是因为每次切换都用 replace 但没调用 addToBackStack导致旧 Fragment 的被替换后本应销毁但因为 replace 内部的事务没有正确清理旧 Fragment 的引用导致旧实例泄漏。解决办法其实很简单确保 replace 之前没有把 Fragment 加进回退栈并且容器在替换时要把旧 Fragment 移除干净。4. Fragment 状态保存与恢复4.1 状态保存在哪个环节Fragment 的状态保存发生在 Activity 的 onSaveInstanceState 阶段。但 Fragment 不是直接把自己的状态写进 Activity 的 Bundle而是通过 FragmentManager 统一收集。FragmentManager 会遍历 mActive 列表里所有 Fragment调用每个 Fragment 的 performSaveInstanceState把每个 Fragment 的状态打包成一个 Bundle再放进一个大的 Bundle 里最后交给 Activity 一起保存。这个状态包里包含的内容有Fragment 的额外状态通过 onSaveInstanceState 自己存的数据Fragment 的视图状态例如 EditText 里的文本、列表滚动位置Fragment 当前的状态等级Fragment 的 mWho、mTag、mFragmentId 等标识信息回退栈相关状态视图状态的保存比较特殊Fragment 会给自己的视图根节点设置一个唯一的 ID作为 View 状态保存在 SparseArray 里的 key。恢复时再根据这个 key 从 SparseArray 里取回状态。这也是为什么 Fragment 的布局根节点最好设置一个 ID否则视图状态恢复可能会错乱。4.2 重建时 Fragment 是从 newInstance 来的吗这是 Fragment 恢复机制里最迷人也最容易踩坑的部分。当系统因为配置变更比如旋转屏幕或进程被杀死后重建 Activity 时Activity 会通过 FragmentManager 的 restoreSaveState 恢复之前保留下来的 Fragment 事务和状态。FragmentManager 会读取保存的 FragmentState 数组根据里面记录的类名通过反射创建 Fragment 实例并重新组装 FragmentManager 内部的数据结构。也就是说重建后的 Fragment 实际上不是你在代码里 new 出来的实例而是反射创建出来的新实例。所以你再怎么用 newInstance 传参数都没用参数必须放在 Fragment 的 arguments 里系统恢复时会自动把 arguments 传给新的实例。这个机制导致了一个特别常见的坑用空构造器约束。系统反射创建 Fragment 时依赖无参构造器如果你在 Fragment 里定义了一个带参构造器并且没有显式提供无参构造器恢复时直接抛异常。如果进程被杀死前Fragment 被 add 进回退栈时设置了 tag恢复后这个 tag 也会被还原。所以你在恢复后通过 findFragmentByTag(example) 能找到那个重新创建的 Fragment。4.3 谁在管理 Fragment 的 mWho 和唯一标识每个 Fragment 都有一个唯一的标识 mWho它是在 FragmentManager 把 Fragment 加进 mActive 时生成的。mWho 的生成规则比较简单由宿主的标识加一个递增的数字组成例如 0#1、0#2。这个 mWho 是 Fragment 内部的重要标识它被用来生成 Fragment 默认的 Tag也用于 ViewModelStore 的 key。Fragment 的 ViewModel 就是通过 mWho 来标识作用域的。所以 Fragment 重建后即使 Fragment 实例变了只要 mWho 没变就能拿到同一个 ViewModelStore从而实现 Fragment 内 ViewModel 在配置变更时数据不丢失。这个设计非常关键但很多开发者并不知道 ViewModel 能否恢复的秘密就在这里。5. 并发修改与重复操作Fragment 实现里的两大隐患5.1 为什么会出现 Fragment already added 和 No fragment idFragment 使用中最常见的两个异常IllegalStateException: Fragment already addedIllegalArgumentException: No fragment id found for fragment这两个异常本质上都是因为对 FragmentManager 的状态管理不够理解导致的操作冲突。Fragment already added 出现的原因是你试图把同一个 Fragment 实例 add 两次。比如你在某个方法里写了 add(fragment)但这个方法可能被多次调用因为 FragmentManager 会校验 Fragment 的 mAdded 标识。解决方案有两个一是每次用 new 创建新实例再 add二是 add 之前先判断 fragment.isAdded()。No fragment id found 出现的原因是你试图用 replace 或 add 到一个不存在的容器 ID 上。比如容器 View 的 ID 写错了或者这个容器所在的布局还没有被 inflate 出来。这种问题最常见的场景是在 Activity 的 onCreate 里直接操作一个还没加载出来的子布局里的容器。5.2 commit 和 executePendingTransactions 的使用时机commit 是异步的executePendingTransactions 会强制立即执行所有待处理的事务。但强制立即执行并不意味着安全。如果在 Activity 的 onSaveInstanceState 之后调用 commit会直接抛出 IllegalStateException因为此时 FragmentManager 已经保存过状态无法再安全地修改。所以 Google 在 FragmentTransaction 上提供了 commitAllowingStateLoss()让你即使在这个阶段也能提交事务。但这个方法是双刃剑它会把状态丢失的风险交给你自己承担。比如你在 onSaveInstanceState 之后 commit 了一个 Fragment进程被系统杀死后这个 Fragment 可能不会被恢复导致下次启动时界面状态和预想不一致。我个人的习惯是凡是在生命周期回调之外提交 Fragment 事务都先判断当前时机是否安全。如果是 DialogFragment 的点击事件里提交没问题如果在异步回调里提交就要注意宿主 Activity 是否已经 stop 或 saveInstanceState。6. 嵌套 Fragment 与 ChildFragmentManager6.1 嵌套 Fragment 是如何管理的每个 Fragment 内部都可以有自己的子 Fragment这些子 Fragment 由 ChildFragmentManager 管理而不是 FragmentManager。ChildFragmentManager 是 FragmentManager 的一个特化实例每个 Fragment 实例都持有一个自己的 ChildFragmentManager。这带来的结果是子 Fragment 的生命周期完全由父 Fragment 控制。父 Fragment 走到 RESUMED子 Fragment 才可能 RESUMED父 Fragment 被销毁子 Fragment 也会被销毁。这种层级派生关系在源码里是通过 Fragment 的 mChildFragmentManager 和 FragmentManager 的 mParent 互相引用来实现的。嵌套 Fragment 在开发时有个容易踩的坑用 getActivity().getSupportFragmentManager() 去操作子 Fragment或者用 getFragmentManager() 去找子 Fragment 找不到。正确的方式是(getChildFragmentManager())。如果父 Fragment 是在 ViewPager2 里那每个页面 Fragment 里的子 Fragment 管理也是通过 getChildFragmentManager()。6.2 子 Fragment 的状态保存也走同一个体系嵌套 Fragment 的状态保存同样由 FragmentManager 统一处理但区别在于子 Fragment 的状态是保存在父 Fragment 的状态包里的。父 Fragment 的 performSaveInstanceState 会把 ChildFragmentManager 收集到的状态写进一个专门的字段然后一起打包。恢复时也类似父 Fragment 恢复时会先从自己的状态包里取出 ChildFragmentManager 的状态再让 ChildFragmentManager 去恢复它的 mActive 列表。这就是为什么嵌套 Fragment 在配置变更后能够保持完整状态的原因。6.3 嵌套 Fragment 的常见病IllegalStateException 和重复添加嵌套 Fragment 最常见的异常有两种一个是上面提到的 Fragment already added另一个是 Fragment no longer exists for key。后者通常出现在父 Fragment 被销毁但子 Fragment 还试图恢复时。我还遇到过一个很隐蔽的问题在 ViewPager2 的 Fragment 里再嵌套一个 Fragment每次滑动到该页面时都重新 add 一个新的子 Fragment导致子 Fragment 视图不断叠加。后来排查发现原因是 Fragment 的 onCreateView 被多次调用add 操作也在 onCreateView 里执行了。正确做法是先在 onCreate 里判断 childFragmentManager 里是否已经有这个 Fragment如果没有才 add。7. Fragment 的性能优化与内存泄漏排查7.1 Fragment 的生命周期比 Activity 更频繁Fragment 的生命周期事件比 Activity 多很多尤其是在 View 销毁和创建上。onCreateView 和 onDestroyView 几乎在每次切换、动画、重建时都会被调用。如果你在 onCreateView 里有大量资源初始化操作或者 inflate 了一个非常复杂的布局都会直接影响页面切换的流畅度。针对这种情况我一般有两种处理思路布局方面尽量用 ConstraintLayout 减少层级避免在 Fragment 里嵌套太多深层次的 LinearLayout。逻辑方面把和视图无关的数据初始化放到 onCreate 而不是 onCreateView把网络请求和数据库读写放到 onStart 之后避免频繁重复请求。7.2 常见内存泄漏点Fragment 相关的内存泄漏最常见的几个点在 Fragment 里持有 Activity 的引用不释放。比如一个静态变量持有当前 Activity这种在 Fragment 销毁时Activity 就可能泄漏。Handler 或 Runnable 在 Fragment 销毁后还持有 Fragment 的引用。异步回调retrofit、RxJava在 Fragment 销毁后成功回调如果不做判空直接操作 View 就会崩或者导致 Fragment 无法被回收。Dialog 和 BottomSheet 相关的对象在 Fragment 销毁后没 dismiss。排查内存泄漏的常用手段就是用 Android Studio 的 Memory Profiler 或 LeakCanarydump 出堆栈看谁持有 Fragment 引用。还有一个我每次都会提醒新人的点在 Fragment 的 onDestroyView 里一定要把对 View 的引用置为 null。因为 Fragment 的视图销毁后如果 View 还挂在 window 上或者被某个对象引用外层 Fragment 就不会被回收。8. 常见问题排查与避坑手册8.1 为什么 onCreate 后 onViewCreated 空指针这个问题的原因通常是你在 Fragment 里过度依赖 onCreateView 返回的 View。onCreateView 和 onViewCreated 之间有一个完整的 view 创建过程如果你在 onCreateView 里就把返回的 view 赋值给一个成员变量没问题。但如果你在 onCreateView 里就对某些子 View 调用了 findViewById而在 onViewCreated 时又去操作就可能因为时序问题拿到 null。更安全的写法是onCreateView 只负责 inflate 布局onViewCreated 里统一做 findViewById 和视图初始化。不要在 onCreateView 里做太多的事情尤其不要访问依赖子 Fragment 已经创建的视图。8.2 进程被杀死恢复时 Fragment 重复创建这个问题一般出现在你没有正确处理状态的保存和恢复时。比如 Activity 的 Intent 里带了一些参数你根据这些参数创建 Fragment。但系统恢复时不是重新走 onCreate而是直接 restore 之前的状态如果你在 onCreate 里无脑 new 一个 Fragment 再 add就会和恢复出来的 Fragment 叠加。正确写法是if (savedInstanceState null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, new ExampleFragment()) .commit(); }这样系统自动恢复时就不会再重复添加。如果想更严谨可以在 onCreate 里先查找一下是否有之前的 Fragment 实例再决定是否创建。8.3 回退栈弹出后 Fragment 不见了有时候你明明调用了 addToBackStack返回键按了几次之后发现 Fragment 找不到了。这通常是因为你在事务里用了 replacereplace 会先移除旧的 Fragment 再添加新的出栈时再反向操作。如果旧的 Fragment 被 replace 后离开了 mAdded 列表但在回退栈里还有它的恢复记录出栈时它会被重新 add 回来。看起来就像“明明找不到了怎么又回来了”。还有一种情况是 addToBackStack 和 commitAllowingStateLoss 配合使用不当。如果在 Activity 已停止的状态下提交了带 addToBackStack 的事务返回键弹出时可能因为状态还没准备好导致 Fragment 显示异常。这种情况尽量用 commitNow 或在合适的生命周期时机提交。8.4 Fragment 动画导致的空指针Fragment 切换动画时新旧 Fragment 可能同时处于可见状态此时如果你在动画结束时操作旧的 Fragment 视图可能因为视图被清理而崩溃。这种情况在 Fragment 里有动画监听器时尤其容易出现。解决办法是使用官方的动画监听回调 API或者在动画结束后判断 fragment.isRemoving() 再决定是否操作视图。isRemoving() 是一个很实用的方法专门用于判断当前 Fragment 是否正在被移除。8.5 一个通用的排查思路遇到 Fragment 相关的疑难 bug我推荐的排查顺序先看崩溃堆栈确认是哪一个类、哪个生命周期抛出的异常。再查所有事务提交的地方判断是否有重复 add、replace、popBackStack 的时序问题。然后检查 FragmentManager 的状态可以用反射或者调试工具查看 mActive 和 mAdded 列表里有几个 Fragment。最后怀疑状态恢复问题看一下 savedInstanceState 里保存的数据是不是已经过期。9. 从源码角度看 FragmentManager 的几个关键设计9.1 为什么要让事务变成 Op 的列表Fragment 的事务设计成 Op 列表最大的好处是“可逆”。add 和 remove 互为逆向操作hide 和 show 互为逆向操作detach 和 attach 互为逆向操作。回退栈弹出时只要把这个列表反向执行一遍就能接近完美地还原之前的状态。这种设计还让事务支持批量处理。一次 commit 里可以连续 add 三个 Fragment它们会被统一调度而不是每个 Fragment 独立走一次生命周期。这对性能有明显好处。9.2 mActive 列表为什么不能随意退出mActive 保存了所有活跃的 Fragment 实例包括在回退栈里的。即使某个 Fragment 的视图已经被销毁只要它还在回退栈中或还在 active 状态FragmentManager 就不会清理它。这个设计保证了回退栈弹出后能快速恢复 Fragment而不需要重新创建。但反过来这也带来了内存开销。如果一个 Fragment 在回退栈里待了很久它持有的数据比如大图Bitmap也会一直保留无法被回收。所以如果一个 Fragment 不再需要记得把它从回退栈里移出或者用 popBackStack 清理。9.3 FragmentManager 是线程不安全的这里要特别强调FragmentManager 不是线程安全的。所有对 FragmentManager 的操作都必须发生在主线程。但 commit() 本身是异步的它是把事务投递到主线程的队列里。所以即便你在子线程调用 commit()也不会立刻崩溃但实际操作还是会在主线程执行。因此在子线程里不要直接操作 FragmentManager。如果非要在子线程里更新 Fragment通过 Handler 或 LiveData 切回主线程再做。10. 我对 Fragment 实现原理的几点体会用了这么多年 Fragment踩过各种奇奇怪怪的坑之后我形成了几个核心体会。第一Fragment 的状态迁移模型是整个框架的基石。只要涉及 Fragment 的疑难问题十有八九都能归结为状态问题。所以与其背生命周期回调的顺序不如去理解状态的升降规则以及每个状态对应的视图和数据是否还在。第二回退栈和状态保存这两个机制是 Fragment 的精华。很多人抱怨 Fragment 难用其实很多时候是他们没有正确使用这两个机制。我建议每个 Android 开发者都花时间读一下 FragmentManager 和 BackStackRecord 的源码读完之后很多以前觉得莫名其妙的现象都能解释通。第三合理使用嵌套 Fragment 和 ChildFragmentManager可以构建出非常灵活的 UI 架构。但嵌套层级不要太深超过三层就很难管理了。另外 ViewPager2 和 Fragment 的组合是目前的标配但记住 ViewPager2 默认会预加载相邻页面这会放大 Fragment 状态管理的复杂度。最后一点能不用 Fragment 就不要用 Fragment。如果你的界面逻辑足够简单一个 Activity 自定义 View 完全够用没必要为了用而用。Fragment 的价值在于跨配置变更的状态保留、生命周期管理和大屏适配。当你的项目确实需要这些能力时才有理由引入 Fragment。否则每多一个 Fragment就多一分状态管理的复杂度。回到开头的那个问题Fragment 实现原理能给我们带来什么好处在我看来它最大的价值不是让你写出更炫酷的代码而是让你在面对复杂界面状态时心里有一个清晰的图谱这个 Fragment 现在处于什么状态它手头有哪些数据它即将往哪个状态迁移以及它一旦出错问题会出在哪一步。有了这个图谱你在 Android 里的很多开发问题都会变得简单很多。