如何区分popBackStack()和replace()操作的不同之处?

27

我在管理Fragment时遇到了一些奇怪的行为,并想知道SO是否能帮助解释为什么会出现这种情况。

我有两个Fragment,我们称它们为Fragment A和Fragment B。应用程序的一般流程是当用户以某种方式与Fragment A交互时,通过调用fragmentTransaction.replace()显示Fragment B(在所有情况下都会发生)。当我显示Fragment B时,我将Fragment A添加到后退堆栈;然后,当用户在Fragment B上按下返回按钮时,通过从后退堆栈中弹出来重新显示Fragment A。

这很好,但今天我发现从Fragment B中存在一种流程,它调用fragmentTransaction.replace(),将Fragment B替换为当前在后退堆栈中的Fragment A实例。

本身并没有任何问题,但是当我从Fragment A返回到Fragment B时,奇怪的行为出现了。如果我调用fragmentTransaction.replace(),则不会调用Fragment B的onCreate()方法。

但是,如果我从后退堆栈中弹出Fragment A,然后用Fragment B替换它,则会触发Fragment B的onCreate()方法。为什么会这样呢?

请注意,Fragment A和Fragment B的所有实例都是在它们的宿主Activity启动时创建的。

为了澄清,第二次调用onCreate()的情况如下:附加Fragment A =>替换为Fragment B,将Fragment A添加到后退堆栈中=>使用popBackStack()弹出Fragment A=>再次用Fragment B替换Fragment A。

2个回答

92

replace() 方法有两个作用:

  1. 从你指定的容器(C)中移除当前添加的片段(A)
  2. 将新的片段(B)添加到同一容器中

这两个操作是作为后退栈记录/事务保存的。请注意,片段 A 仍然处于 created 状态,其视图已被销毁。

现在,popBackStack() 方法会撤消您最后添加到后退栈中的事务。

在这种情况下,需要执行两个步骤:

  1. 从 C 中删除 B
  2. 向 C 添加 A

之后,片段 B 变为 detached,如果您没有保留对它的引用,它将被垃圾回收。

回答问题的第一部分,没有 onCreate() 调用,因为 FragmentB 仍处于 created 状态。而回答问题的第二部分稍微复杂。

首先,理解一个重要的点:实际上并不向后退栈添加 Fragments,而是添加 FragmentTransactions。所以当您认为自己“将 Fragment A 替换为 Fragment B,并将 Fragment A 添加到后退栈”时,实际上是将整个操作添加到了后退栈中,并且是以替换 A 为 B 的方式完成的。这个替换包括两个动作 - 删除 A 和添加 B。

然后,下一步是弹出包含此替换的事务。所以你并不是在弹出 FragmentA,而是在撤消“删除 A,添加 B”,它的恢复操作是“删除 B,添加 A”。

最后一步应该更清晰了 - FragmentManager 不知道 B 的存在,因此当您通过将 A 替换为 B 添加它时,B 需要经过早期的生命周期方法 - onAttach()onCreate()

下面的代码说明了正在发生的情况。

FragmentManager fm  = getFragmentManager();
FragmentA fragmentA = new FragmentA();
FragmentB fragmentB = new FragmentB();

// 1. Show A
fm.beginTransaction()
  .add(fragmentA, R.id.container)
  .commit();

// 2. Replace A with B
// FragmentManager keeps reference to fragmentA;
// it stays attached and created; fragmentB goes 
// through lifecycle methods onAttach(), onCreate()
// and so on.
fm.beginTransaction()
  .replace(fragmentB, R.id.container)
  .addToBackstack(null)
  .commit();

// 2'. Alternative to replace() method
fm.beginTransaction()
  .remove(fragmentA)
  .add(fragmentB, R.id.container)
  .addToBackstack(null)
  .commit();

// 3. Reverse (2); Result - A is visible
// What happens:
//   1) fragmentB is removed from container, it is detached now;
//      FragmentManager doesn't keep reference to it anymore
//   2) Instance of FragmentA is placed back in the container
// Now your Backstack is empty, FragmentManager is aware only
// of FragmentA instance
fm.popBackStack();

// 4. Show B
// Since fragmentB was detached, it goes through its early
// lifecycle methods: onAttach() and onCreate().
fm.beginTransaction()
  .replace(fragmentB, R.id.container)
  .addToBackstack(null)
  .commit();

我已经编辑了我的问题,希望能更加清晰明了。你的解决方案中奇怪的一点是,在两种情况下,Fragment B 都似乎进入了分离状态,然后又被恢复了。popBackStack() 必须在做一些不同的事情;我只是好奇那是什么。 - Jonathan
扩展了我的回答,希望这样能澄清事情。片段及其事务是一个相当棘手的主题。 - mindeh
好的,如果我手动用Fragment A替换Fragment B,那么另一个事务记录将被添加到后退栈中。这意味着FragmentManager仍然认为Fragment B处于分离状态。但是,当我弹出唯一包含Fragment B的事务时,FragmentManager认为该Fragment不再附加,因为没有包括它的事务。这是正确的理解吗?此外,感谢您提供超级信息化的帮助。 - Jonathan
很高兴能帮忙!我会逐句回答。(1)我只是提供“2'”作为一种替代方案,其效果与“2”相同,只是为了澄清。(2)我不确定您指的是哪个步骤,但如果您添加了B,并且没有完全从FM中删除它,则它将保持在附加和创建状态。完全删除是指没有任何交易引用它。(3)基本上是的,只是FM使片段不附加,即更改其标志,调用onDetach()等。 - mindeh

0

这可能是因为片段管理器重用了片段实例而不是重新创建它。如果 Fragment B 中的代码需要在显示片段时执行,则将代码移动到另一个方法中,例如 onResume()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接