这是在离开深层嵌套堆栈时清理片段后退堆栈的正确方法吗?

134

我正在使用Android兼容库实现Fragments,并扩展了布局示例,以便一个Fragment包含一个按钮,该按钮会引发另一个Fragment。

在左侧的选择面板中,我有5个可选项目-A B C D E

每个项目都会在详细信息面板中加载一个Fragment(通过FragmentTransaction:replace)-a b c d e

现在,我已经扩展了Fragment e,使其包含一个按钮,该按钮还会在详细信息面板中加载另一个Fragment e1。我已经在Fragment e 的onClick方法中完成了这个操作,如下所示:

FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.replace(R.id.details_frag, newFrag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();

如果我进行以下选择:

E - e - e1 - D - E

然后碎片e在详细信息面板中。这很好,并且符合我的要求。但是,如果此时我点击返回按钮,它不起作用。我必须点击两次,因为e1仍在堆栈中。而且,在点击周围之后,我在onCreateView中得到了一个空指针异常:

为了“解决”这个问题,我添加了以下内容,每当选择A B C D E时:

FragmentManager fm = getActivity().getSupportFragmentManager();
for(int i = 0; i < fm.getBackStackEntryCount(); ++i) {
    fm.popBackStack();
}

只是想知道这是否是正确的解决方案,或者我应该做些不同的事情?

7个回答

258

有几种方法可以实现这个,具体取决于预期的行为方式,但是这个链接应该能够给你提供所有最佳解决方案,而且并不出人意料地来自Dianne Hackborn。

http://groups.google.com/group/android-developers/browse_thread/thread/d2a5c203dad6ec42

基本上你有以下选项:

  • 使用一个名称为初始后退栈状态,并使用 FragmentManager.popBackStack(String name,FragmentManager.POP_BACK_STACK_INCLUSIVE)
  • 使用 FragmentManager.getBackStackEntryCount() / getBackStackEntryAt().getId() 来检索后退栈上第一个条目的 ID,并使用 FragmentManager.popBackStack(int id,FragmentManager.POP_BACK_STACK_INCLUSIVE)
  • FragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) 应该弹出整个后退栈... 我想文档只是错了。(实际上我想文档只是没有涵盖您传递 POP_BACK_STACK_INCLUSIVE 的情况),

5
第二个方法对我有用。对于清除整个堆栈,它的意思是:使用getSupportFragmentManager().popBackStack(getSupportFragmentManager().getBackStackEntryAt(0).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE)方法。 - NickL
@JorgeGarcia 在 popbackstack 后,我们是否可以在不重新启动旧片段的情况下完成当前片段。 - duggu
请注意,除了popBackStack()异步版本之外,还有popBackStackImmediate()版本,这意味着清理工作不会在您调用该方法的确切时刻发生。 - Genc
7
该群组被认为是垃圾邮件并被禁止,我无法访问链接 :( 有人有其他资源吗? - EpicPandaForce
如果我在流程中有多个碎片,例如A-->B--->C-->D,而且从D返回到C,然后返回到B,最后再返回到A。但是,如果我在B/C/D上点击后退按钮,以上选项总是将我带回到A。应该如何正确地管理这种情况? - NGR
显示剩余2条评论

62

如果您不想弹出所有堆栈条目,则另一个干净的解决方案是...

getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
getSupportFragmentManager().beginTransaction().replace(R.id.home_activity_container, fragmentInstance).addToBackStack(null).commit();

这将首先清除堆栈,然后加载一个新片段,因此在任何给定的时间点,您的堆栈中只会有单个片段。


1
这是非常糟糕的:在replace()之前使用getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);,因为调用了onCreateView()onActivityCreated()等加载Fragment的方法。即时恢复和销毁Fragment是不好的,例如Fragment可能会注册一个接收器。这会影响性能。 - VasileM
@VasileM,你能详细描述一下吗?在哪些情况下它是不好的?这是因为FragmentTransaction的异步行为吗?只有错误的片段堆栈会导致性能不佳吗? - CoolMind
@VasileM,这可能是一个很好的实践方法,将观察者和其他任务注册在OnViewCreated Fragment流回调中,或在当前片段被销毁时处理取消注册观察者。' override fun onDestroy() { super.onDestroy() viewModel.viewState.removeObservers(this)}' - Aury0n

28

感谢Joachim的答案,我使用这段代码最终清除了所有的后退栈条目。

// In your FragmentActivity use getSupprotFragmentManager() to get the FragmentManager.

// Clear all back stack.
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
for (int i = 0; i < backStackCount; i++) {

    // Get the back stack fragment id.
    int backStackId = getSupportFragmentManager().getBackStackEntryAt(i).getId();

    getSupportFragmentManager().popBackStack(backStackId, 
        FragmentManager.POP_BACK_STACK_INCLUSIVE);

} /* end of for */

18
为什么在这里使用循环?对我来说,FragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) 清除了所有的返回栈条目... - Marek
3
@Marek,因为有时不是所有的片段都应该被删除,所以循环是合适的。 - CoolMind
2
@CoolMind,我还是不太明白。popBackStack(backStackId, INCLUSIVE)将弹出所有片段(状态)回到backStackId,因此一旦您弹出要弹出的最低i,所有更高的片段应同时弹出。那么循环的意义是什么? - LarsH
@LarsH,你是对的。请参见https://dev59.com/qG025IYBdhLWcg3wyJCV#59158254。为了将其他片段弹出到所需的标记,我们应该首先使用addToBackStack(**tag**)添加片段。 - CoolMind

7

我进行了很多关于清理Backstack的研究,最终看到了“事务Backstack及其管理”。这里是对我来说最有效的解决方案。

 // CLEAR BACK STACK.
    private void clearBackStack() {
        final FragmentManager fragmentManager = getSupportFragmentManager();
        while (fragmentManager.getBackStackEntryCount() != 0) {
            fragmentManager.popBackStackImmediate();
        }
    }

以上方法循环遍历后退栈中的所有事务,并立即一次性删除它们。

注意:上述代码有时不起作用,我会因此遇到ANR问题,请不要尝试这样做。

更新:以下方法从后退栈中删除所有“名称”对应的片段。

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStack("name",FragmentManager.POP_BACK_STACK_INCLUSIVE);
  • name(名称):如果非空,则是要查找的先前后退状态的名称;如果找到,将弹出所有状态直到该状态。可以使用POP_BACK_STACK_INCLUSIVE标志来控制是否弹出命名状态本身。如果为空,则只弹出顶部状态。

我不明白为什么你要一个一个地删除它们。你选择这种解决方案而不是其他解决方案的原因是什么?有没有理由一次只删除一个而不是全部删除? - miva2
目前还没有一种方法可以一次性删除后退栈,请参见http://developer.android.com/reference/android/app/FragmentManager.html。 - Lokesh

3

我使用的代码与使用 while 循环的代码类似,但我在每个循环中调用了入口计数...所以我想它可能会慢一些。

FragmentManager manager = getFragmentManager();
while (manager.getBackStackEntryCount() > 0){
        manager.popBackStackImmediate();
    }

0

正如如何从后堆栈中弹出片段LarsH在此处所述,我们可以使用以下方法从顶部向下弹出多个片段直到特定标签(连同标记的片段):

fragmentManager?.popBackStack ("frag", FragmentManager.POP_BACK_STACK_INCLUSIVE);

用你的片段标签替换“frag”。记住,首先我们应该使用以下代码将片段添加到返回栈中:

fragmentTransaction.addToBackStack("frag")

如果我们使用 addToBackStack(null) 添加片段,那么我们就不能通过那种方式弹出片段。

-4
    // pop back stack all the way
    final FragmentManager fm = getSherlockActivity().getSupportFragmentManager();
    int entryCount = fm.getBackStackEntryCount(); 
    while (entryCount-- > 0) {
        fm.popBackStack();
    }

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