理解 FragmentManager 的 BackStack

587 阅读3分钟

最近, QMUI 的 fragment 管理框架升级到了 0.1.0, 添加了一个新的接口 startFragmentAndDestroyCurrent, 可以方便的完成类似 startActivity 之后 finish 的效果,有兴趣的可以试试:

implementation "com.qmuiteam:arch:0.1.0"

其源码在 QMUI,有兴趣的也可以去看看。

FragmentTransition#addToBackStack 的误用

之前也有人在 issue 中问道如何实现类似 Activity#finish() 的效果,我不加思索的回答到:

FragmentTransition 有提供 addToBackStack(boolean) 方法, 启动前一个 fragment (不是新的那个)时, addToBackStack 传参为 false 就行。

这样初看也确实解决了问题,一切看似完美,直到另一个 issue 的出现。在阅读源码来解决这个问题时,我发现了 addToBackStack(false) 是存在问题的,这篇文章会指出问题所在,然后给出 QMUI 的解决方案。

BackStack 的工作机制

在之前的文章我也有提到, BackStack 并不是将 fragment 保存到堆栈,而是将操作(add, remove等)保存到堆栈,然后在返回时将操作逆着来就行。

在开始之前,先介绍 Fragment 的一个私有成员变量:

Fragment 存在一个成员变量 mBackStackNesting,它是标志 fragment 是否存在于 BackStack 的重要变量,每个 fragment 的每次操作都会影响到它, 只有它的值小于等于0时,fragment 才会走到 onDestroy,从而得到释放

一般情况下,我们切换 fragment 时 BackStack 的变更行为为:

现在,让我们来看看使用 addToBackStack(false) 时会发生什么:

回到之前提到的 issue。其出错的原因是 findCurrentFragment 出错,而 findCurrentFragment 会先在被添加到 FragmentManager 中的 fragment 队列中去寻找,根据上面的描述,addToBackStack(false)会导致页面存在多个 fragment, 所以猜测可能是它导致的问题,但我也不能确定我的猜测 100% 正确,因为我并没能在开发环境中重现。

QMUI 的 fix 方案。

现在来说说 QMUI 的 fix 方案。其实也很简单,我们直接对 BackStack 的最后一个 Entity 做一些修正(以上图为例):

  1. 修改 Op[addB] 替换为 Op[addC]
  2. 将 B.mBackStackNesting 赋值给 C,完成 BackStack 中对 B 的替换
  3. B.mBackStackNesting减一 或者将 B.mBackStackNesting 置为 0, 使B在开启新界面时得到释放。

做完上述操作,才算真正的将 B destroy 掉而且保证堆栈的正确性。 除此,startFragmentAndDestroyCurrent 还提供了第二个参数, 这个参数是做什么的呢?是用来控制转场动画的。假设 A->B 使用 startFragment 和转场动画 a,B->C 使用 startFragmentAndDestroyCurrent和转场动画 b,那么从 C 返回到 A 时,该使用转场动画 a 还是 转场动画 b 呢?这就取决于你第二个参数传什么了。

内容就这么多了,就不贴源码了,是一堆的反射而已。如果有兴趣的话,就去 QMUI 看看吧。最近对文档的呼声越来越多了,哎,暂时被需求和 bug 压得死死的,有闲暇时间我们会尽量补上的,继续奋斗。

原文链接:blog.cgsdream.org/2018/04/21/…