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~28API 29+
onAttach同步调用同步调用同步调用
onCreate同步调用同步调用同步调用
onCreateView同步调用同步调用同步调用
onActivityCreated在Activity.onCreate后调用在Activity.onCreate后调用已废弃,不保证时序
onStart同步调用同步调用同步调用
onResume同步调用同步调用同步调用

我在一个老项目里,用了 onActivityCreated 来初始化一些 DialogFragment 的依赖。结果跑在 Android 10 上,偶尔会出现空指针。排查了半天才发现是时序变了。后来我干脆把所有初始化都挪到 onViewCreated 里,一劳永逸。

注意:如果你的 minSdk 低于 29,并且用到了 onActivityCreated,建议尽快迁移到 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。

小经验:我用过的项目里,back stack tag 统一用 Fragment 类名作为 tag,调试起来非常方便。比如 addToBackStack("MyFragment")。

4.3 旧版 support 库与新版 androidx 迁移

从 android.support 迁移到 androidx,不是简单改个依赖就行。生命周期相关的类名、包名、方法签名都变了。

4.3.1 迁移的必须步骤

  1. 在 gradle.properties 里加上 android.useAndroidX=true 和 android.enableJetifier=true。
  2. 把所有 android.support.* 替换成 androidx.*。常见的有:
旧 support新 androidx
android.support.v4.app.Fragmentandroidx.fragment.app.Fragment
android.support.v4.app.FragmentActivityandroidx.fragment.app.FragmentActivity
android.support.v4.app.FragmentManagerandroidx.fragment.app.FragmentManager
android.support.v4.app.FragmentTransactionandroidx.fragment.app.FragmentTransaction
android.support.annotation.NonNullandroidx.annotation.NonNull
  1. 如果你用了 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生命周期适配核心流程 Activity.onCreate() Fragment.onAttach() Fragment.onCreate() API<29? onActivityCreated可用 onActivityCreated废弃 推荐:onViewCreated() 迁移到AndroidX 关键点:使用onViewCreated替代onActivityCreated,用ViewModel替代retainInstance,统一back stack tag为类名 FragmentTransaction中优先用commit(),仅在异步回调时用commitAllowingStateLoss()并配合isStateSaved()判断

总的来说,Fragment 的生命周期适配,关键就三条:

  • 所有初始化逻辑统一走 onViewCreated,别再用 onActivityCreated。
  • FragmentTransaction 一定配上状态保存检查。
  • 如果还在用 support 库,赶紧迁移到 androidx。别拖,拖到后面改的代码更多。
最后说一句:我曾经在一个日活百万的项目里,因为 fragment 生命周期问题引发了大量崩溃。后来花了三天把所有 onActivityCreated 改成 onViewCreated,再配合 ViewModel 管理数据,崩溃率直接从 0.3% 降到了 0.01% 以下。这个改造其实不难,收益却很大。