4. Fragment与Activity生命周期适配:不同API级别下的差异与迁移
说实话,Fragment 的生命周期,是 Android 开发里最容易踩坑的地方之一。我在好几个项目里都遇到过线上崩溃,追根溯源都是 Fragment 生命周期没处理好。尤其是 API 级别变了、support 库升级到 androidx 之后,老代码动不动就出问题。
今天咱们就好好捋一捋。从 API 差异、FragmentTransaction 的兼容写法,到 support 库迁移到 androidx 的实战步骤,一个个来过。
4.1 不同 API 级别下的生命周期差异
先问个问题:Fragment 的 onCreateView、onActivityCreated、onResume 这些方法,在 Android 4.x 和 10.x 上执行顺序一样吗?
我直接告诉你:不一样。
最典型的差异出现在 onActivityCreated 这个回调上。API 28(Android 9)及之前,这个回调在 Activity 的 onCreate 完成之后、Fragment 的 onStart 之前触发。但到了 API 29+,Google 把这个回调标记为 deprecated,理由是不再保证时序。
看个表你就明白了:
| 回调方法 | API 23 之前 | API 23~28 | API 29+ |
|---|---|---|---|
| onAttach | 同步调用 | 同步调用 | 同步调用 |
| onCreate | 同步调用 | 同步调用 | 同步调用 |
| onCreateView | 同步调用 | 同步调用 | 同步调用 |
| onActivityCreated | 在Activity.onCreate后调用 | 在Activity.onCreate后调用 | 已废弃,不保证时序 |
| onStart | 同步调用 | 同步调用 | 同步调用 |
| onResume | 同步调用 | 同步调用 | 同步调用 |
我在一个老项目里,用了 onActivityCreated 来初始化一些 DialogFragment 的依赖。结果跑在 Android 10 上,偶尔会出现空指针。排查了半天才发现是时序变了。后来我干脆把所有初始化都挪到 onViewCreated 里,一劳永逸。
4.2 FragmentTransaction 兼容处理
FragmentTransaction 是 Fragment 操作的入口。但不同版本的 support 库和 android x 里,它的行为也有细微差异。
4.2.1 commit 与 commitAllowingStateLoss
很多新人会被这两个方法搞蒙。简单说:commit() 在 Activity onSaveInstanceState 之后调用会抛异常;commitAllowingStateLoss() 不会抛,但可能导致 Fragment 状态丢失。
我个人习惯:
如果是在用户主动操作(比如点击按钮)后替换 Fragment,用 commit(),因为此时 Activity 肯定在前台;
如果是在后台异步回调(比如网络请求完成)里做 Fragment 操作,用 commitAllowingStateLoss(),但要自己额外判断 isAdded() 和 isStateSaved()。
// 安全的 Fragment 操作写法
public void safeFragmentTransaction(FragmentManager fm, Runnable transaction) {
if (fm != null && !fm.isStateSaved()) {
transaction.run();
} else {
// 记录日志,稍后重试或忽略
Log.w("FragmentTx", "状态已保存,放弃本次操作");
}
}
4.2.2 addToBackStack 的差异
在 Support Library 旧版(22.0 之前),addToBackStack(null) 的入参如果传 null,后面按返回键时 Fragment 会回退到上一个状态,但 FragmentManager 不会再调度生命周期。这导致有些 Fragment 的 onResume 不会被调用。
AndroidX 修复了这个问题。如果你还在用旧 support 库,建议给 back stack 打上 tag 字符串,不要传 null。
addToBackStack("MyFragment")。
4.3 旧版 support 库与新版 androidx 迁移
从 android.support 迁移到 androidx,不是简单改个依赖就行。生命周期相关的类名、包名、方法签名都变了。
4.3.1 迁移的必须步骤
- 在 gradle.properties 里加上
android.useAndroidX=true和android.enableJetifier=true。 - 把所有
android.support.*替换成androidx.*。常见的有:
| 旧 support | 新 androidx |
|---|---|
| android.support.v4.app.Fragment | androidx.fragment.app.Fragment |
| android.support.v4.app.FragmentActivity | androidx.fragment.app.FragmentActivity |
| android.support.v4.app.FragmentManager | androidx.fragment.app.FragmentManager |
| android.support.v4.app.FragmentTransaction | androidx.fragment.app.FragmentTransaction |
| android.support.annotation.NonNull | androidx.annotation.NonNull |
- 如果你用了 Fragment 的 retainInstance,注意:androidx 里
setRetainInstance(true)在 API 28+ 被废弃,推荐用 ViewModel 替代。我在迁移时踩过这个坑,retainInstance 保留下来的对象在 configuration change 里没问题,但进程被杀后就没了。ViewModel 是正经的生命周期感知组件,推荐迁移。
4.3.2 迁移后的生命周期行为变化
androidx 的 Fragment 生命周期和 support 库在大部分场景下一致,但有一个重要区别:
support 库中,onActivityCreated 在 FragmentActivity 的 onCreate 中触发;
androidx 中,onActivityCreated 被标记为废弃,建议改用 onViewCreated 配合 Lifecycle 观察。
另外,FragmentManager 的 executePendingTransactions() 在 androidx 里行为更严格:某些情况下会直接调用 doFrame 刷新 UI,如果你在 onResume 里调它,可能引起嵌套提交异常。我建议尽量用 commit() 后等 loop 处理,而不是手动调用 executePendingTransactions。
4.4 生命周期适配最佳实践总结
最后,放一张我梳理的流程图。它把不同 API 级别下 Fragment 与 Activity 的生命周期关系、迁移要点串在一起了。
总的来说,Fragment 的生命周期适配,关键就三条:
- 所有初始化逻辑统一走
onViewCreated,别再用onActivityCreated。 - FragmentTransaction 一定配上状态保存检查。
- 如果还在用 support 库,赶紧迁移到 androidx。别拖,拖到后面改的代码更多。