FragmentTransaction 基础 API 详解

FragmentTransaction 是 Android 中管理 Fragment 添加、移除、替换、隐藏等操作的核心类。它提供了多个提交事务的方法,理解 commit、commitNow、commitAllowingStateLoss 和 executePendingTransactions 的差异,是避免 IllegalStateException、提升界面响应性的关键。

核心要点:所有事务默认是异步提交到主线程消息队列,但 commitNow 会立即同步执行。选择哪个 API 取决于你对执行时机和状态丢失风险的容忍度。

1. 四种 API 对比

API执行方式执行时机状态丢失风险典型场景
commit()异步主线程下一轮 Looper 处理抛出 IllegalStateException常规 UI 操作(Activity 可见时)
commitNow()同步立即在当前线程执行抛出 IllegalStateException需要立即生效的事务(如 Fragment 嵌套切换)
commitAllowingStateLoss()异步主线程下一轮 Looper 处理允许状态丢失(部分场景安全)onResume 之后、非关键数据更新
executePendingTransactions()同步强制立即执行所有未提交的事务不产生新事务,仅执行已有需要确认事务完成后再继续逻辑

2. 原理:事务提交与 Activity 状态的关系

FragmentManager 内部维护一个待执行事务队列。当 Activity 状态变为 onSaveInstanceState 之后,commit() 和 commitNow() 会抛出 IllegalStateException,因为系统无法保证后续恢复时事务仍有效。而 commitAllowingStateLoss() 跳过了这个检查,但可能导致 Fragment 状态与保存的实例不一致。

机制图:下方 SVG 展示了不同 API 在 Activity 生命周期中的提交点与执行路径。
FragmentTransaction API 执行时机与路径 Activity 可见状态 onSaveInstanceState 后 commit() 异步 commitNow() 同步 commitAllowingStateLoss() executePendingTransactions() 主线程消息队列 立即执行(当前线程) 异步执行(允许丢状态) 强制刷新队列 只能使用 commitAllowingStateLoss 的时机:onSaveInstanceState 之后、onResume 之前;其余情况优先使用 commit() 或 commitNow()

3. 实现流程与代码示例

典型的事务提交流程如下:

  1. 获取 FragmentManager(support / androidx)
  2. 调用 beginTransaction() 获得 FragmentTransaction 对象
  3. 执行 add、replace、remove 等操作
  4. 调用 commit()、commitNow() 或 commitAllowingStateLoss() 提交
  5. 可选:addToBackStack(null) 加入回退栈
标准安全提交示例(Activity 内):
// 在 onResume 之后、onSaveInstanceState 之前调用
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_container, new MyFragment());
ft.addToBackStack(null);
ft.commit();

4. 使用 commitNow() 同步提交

当需要立即执行事务(如在 Fragment 嵌套切换中确保子 Fragment 立即显示),使用 commitNow() 避免因异步延迟导致的布局抖动。

// 同步执行,当前线程直接完成事务
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.child_container, childFragment);
ft.commitNow();

5. 在状态保存后安全提交

如果一定需要在 onSaveInstanceState 之后执行事务(例如异步回调),必须使用 commitAllowingStateLoss(),并接受可能的状态不一致风险。

// 例如在 AsyncTask 回调中
if (!isFinishing()) {
    getSupportFragmentManager().beginTransaction()
        .add(R.id.container, new LazyFragment())
        .commitAllowingStateLoss();
}

6. 调试与状态检查

  • 使用 FragmentManager.isStateSaved() 检查当前是否可以安全调用 commit()。
  • 调用 executePendingTransactions() 强制立即执行排队的事务,便于调试时确认事务已生效。
  • 启用 Fragment 日志:FragmentManager.enableDebugLogging(true);
// 调试示例:在 onResume 中检查状态并安全提交
if (!fm.isStateSaved()) {
    fm.beginTransaction().add(...).commit();
} else {
    // 使用 allowStateLoss 版本
    fm.beginTransaction().add(...).commitAllowingStateLoss();
}

7. 常见错误与解决方案

错误原因解决
IllegalStateException: Can not perform this action after onSaveInstanceState在 Activity 状态已保存后尝试 commit() 或 commitNow()使用 commitAllowingStateLoss() 或确保操作在 onResume 之前触发
IllegalStateException: Fragment already added多次提交同一个 Fragment 实例检查 Fragment 是否已被添加,使用 findFragmentByTag 复用
事务未生效(UI 不更新)commit() 异步执行,但后续代码依赖立即生效使用 commitNow() 或 executePendingTransactions()

8. 性能与稳定性优化建议

  • 减少事务数量:多次操作合并到同一个 FragmentTransaction 中,避免多次提交。
  • 避免在主线程之外提交:所有 FragmentTransaction 操作必须在主线程执行,否则抛出异常。
  • 善用 commitNow():在不需要回退栈且要求立即生效的场景(如 ViewPager 内的 Fragment 切换),使用 commitNow() 替代 commit() 可减少一次 Looper 轮询延迟。
  • 规范使用 addToBackStack:仅当需要回退时才加入返回栈,否则减少 FragmentManager 的栈维护开销。
  • 检查 isStateSaved:在异步回调中总是先判断状态,避免崩溃。
注意:切勿在 onCreate 或 onActivityCreated 中调用 commitNow(),因为此时 FragmentManager 尚未完全初始化,可能引发未定义行为。建议使用 commit() 并依赖下一帧的异步执行。

9. 完整实践模板

public class SafeFragmentActivity extends AppCompatActivity {
    private void safeAddFragment(Fragment fragment, String tag) {
        FragmentManager fm = getSupportFragmentManager();
        if (fm.isStateSaved()) {
            // 状态已保存,使用允许丢失的版本
            fm.beginTransaction()
                .add(R.id.container, fragment, tag)
                .commitAllowingStateLoss();
        } else {
            fm.beginTransaction()
                .add(R.id.container, fragment, tag)
                .addToBackStack(null)
                .commit();
        }
    }

    // 需要立即生效的同步提交
    private void syncReplaceFragment(Fragment fragment) {
        getSupportFragmentManager().beginTransaction()
            .replace(R.id.container, fragment)
            .commitNow();
    }
}