Problems with Android Fragment back stack

我有一个巨大的问题,与方式的机器人片段回栈似乎工作,将最感激的任何帮助提供。

假设你有3个碎片

[1] [2] [3]

I want the user to be able to navigate [1] > [2] > [3] but on the way back (pressing back button) [3] > [1].

As I would have imagined this would be accomplished by not calling addToBackStack(..) when creating the transaction that brings fragment [2] into the fragment holder defined in XML.

实际情况似乎是,如果我不希望 [2]在用户按下 [3]上的返回按钮时再次出现,我就不能在显示片段 [3]的事务中调用 addToBackStack。这似乎完全违反直觉(也许来自 iOS 世界)。

Anyway if i do it this way, when I go from [1] > [2] and press back I arrive back at [1] as expected.

如果我去 [1] > [2] > [3],然后按回我跳回到 [1](如预期)。 现在,当我尝试再次从 [1]跳到 [2]时,奇怪的行为发生了。首先,在 [2]进入视野之前,简要地显示 [3]。如果我按回在这一点 [3]显示,如果我再次按回应用程序退出。

有人能帮我理解一下这里发生了什么吗?


下面是我主要活动的布局 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >


<fragment
android:id="@+id/headerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
class="com.fragment_test.FragmentControls" >
<!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
android:id="@+id/detailFragment"
android:layout_width="match_parent"
android:layout_height="fill_parent"


/>



更新 这是我用来构建导航层次结构的代码

    Fragment frag;
FragmentTransaction transaction;




//Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
frag = new Fragment1();


transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.commit();


//Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
frag = new Fragment2();


transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack(null);
transaction.commit();




//Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2]
frag = new Fragment3();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.commit();




//END OF SETUP CODE-------------------------
//NOW:
//Press back once and then issue the following code:
frag = new Fragment2();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack(null);
transaction.commit();


//Now press back again and you end up at fragment [3] not [1]

非常感谢

147143 次浏览

我认为,当我读到你的故事时,[3]也在后面。这就解释了为什么你会看到它闪现。

解决方案是永远不要在堆栈上设置[3]。

看起来好像片段[3]并没有从视图中删除,当背面被按下时,所以你必须手动完成它!

首先,不要使用 replace(),而是分别使用 delete 和 add。似乎 replace()不能正常工作。

下一步是覆盖 onKeyDown方法,并在每次按下后退按钮时删除当前片段。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
{
this.finish();
return false;
}
else
{
getSupportFragmentManager().popBackStack();
removeCurrentFragment();


return false;
}






}


return super.onKeyDown(keyCode, event);
}




public void removeCurrentFragment()
{
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();


Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);




String fragName = "NONE";


if (currentFrag!=null)
fragName = currentFrag.getClass().getSimpleName();




if (currentFrag != null)
transaction.remove(currentFrag);


transaction.commit();


}

解释: 这里发生了什么?

If we keep in mind that .replace() is equal with .remove().add() that we know by the documentation:

替换添加到容器中的现有片段。这基本上等同于对所有当前添加的片段调用 remove(Fragment),这些片段是用相同的 containerViewId添加的,然后用这里给出的相同参数添加的 add(int, Fragment, String)

接下来发生的事情是这样的(我在碎片上加了一些数字,这样就更清楚了) :

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view


// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view


// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(这里所有误导性的东西开始发生)

记住,.addToBackStack()只保存 transaction,而不是 碎片本身! 所以现在我们有 frag3的布局:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)


// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view


< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view


< press back button >
// no more entries in BackStack
< app exits >

可能的解决办法

考虑实现 FragmentManager.BackStackChangedListener来监视回栈中的更改,并在 onBackStackChanged()方法中应用您的逻辑:

首先感谢@Arvis 提供的令人大开眼界的解释。

对于这个问题,我更喜欢不同的解决方案,而不是这里公认的答案。我不喜欢干扰重写后退行为,除非这是绝对必要的。当我尝试自己添加和删除片段时,如果按下后退按钮,默认的后退堆栈就会弹出,我发现自己处于片段地狱:)如果你。在移除 f1时,add f2 over f1不会调用任何回调方法,如 onResume、 onStart 等,这可能非常不幸。

不管怎样,我是这么做的:

目前显示的只有片段 f1。

F1-> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

nothing out of the ordinary here. Than in fragment f2 this code takes you to fragment f3.

F2-> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

通过阅读文档,我不确定这是否可行,这个 poping 事务方法被认为是异步的,也许更好的方法是调用 popBackStackImate ()。但就我的设备而言,它运行得完美无瑕。

上述替代方案是:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

这里实际上会有一个简短的回到 f1然后再转到 f3,所以这里有一个小小的故障。

这实际上就是您需要做的全部工作,不需要覆盖回堆栈行为..。

我有一个类似的问题,我有3个连续的 碎片在相同的 Activity[ M1.F0]-> [ M1.F1]-> [ M1.F2] ,然后调用一个新的 Activity[ M2]。如果用户在[ M2]中按下一个按钮,我想返回到[ M1,F1] ,而不是[ M1,F2] ,这是反向按压行为已经做到的。

为了实现这一点,我删除[ M1,F2] ,调用 show on [ M1,F1] ,提交事务,然后通过使用 hide 调用它来添加[ M1,F2]。这消除了额外的背压,否则会留下。

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

嗨 完成这段代码之后: 我不能看到在按 BackKey 时 Fragment2的值。 My Code:

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);


ft.add(R.id.frame, f2);
ft.addToBackStack(null);


ft.remove(f2);
ft.add(R.id.frame, f3);


ft.commit();


@Override
public boolean onKeyDown(int keyCode, KeyEvent event){


if(keyCode == KeyEvent.KEYCODE_BACK){
Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
FragmentTransaction transaction = getFragmentManager().beginTransaction();


if(currentFrag != null){
String name = currentFrag.getClass().getName();
}
if(getFragmentManager().getBackStackEntryCount() == 0){
}
else{
getFragmentManager().popBackStack();
removeCurrentFragment();
}
}
return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);


if(currentFrag != null){
transaction.remove(currentFrag);
}
transaction.commit();
}

我知道这是一个老问题,但我有同样的问题,并解决这样:

首先,在 BackStack 中添加名称(例如“ Frag1”) :

frag = new Fragment1();


transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

然后,无论什么时候你想回到 Fragment1(即使在它上面添加了10个片段之后) ,只要调用 popBackStackInstate 命名:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Hope it will help someone :)

After @Arvis reply i decided to dig even deeper and I've written a tech article about this here: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

给那些懒惰的开发人员。我的解决方案是总是将事务添加到回栈中,并在需要时(自动)执行额外的 FragmentManager.popBackStackImmediate()

这段代码只有很少的几行代码,在我的例子中,如果用户没有深入到堆栈中(例如从 C 导航到 D) ,我想从 C 跳到 A 而不跳回到“ B”。

因此,所附的代码将按以下方式工作 A-> B-> C (背)-> A & A-> B-> C-> D (后)-> C (后)-> B (后)-> A

哪里

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

按问题所述由「 B 」发出至「 C 」。

好的,好的,这是密码:)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;


fragmentManager.beginTransaction()
.replace(R.id.content, fragment, tag)
.addToBackStack(tag)
.commit();


fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
int nowCount = fragmentManager.getBackStackEntryCount();
if (newBackStackLength != nowCount) {
// we don't really care if going back or forward. we already performed the logic here.
fragmentManager.removeOnBackStackChangedListener(this);


if ( newBackStackLength > nowCount ) { // user pressed back
fragmentManager.popBackStackImmediate();
}
}
}
});
}

如果您正在与 addToBackStack ()和 popBackStack ()斗争,那么只需使用

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

在你的 Activity In OnBackPress ()中,通过标签找到农场,然后做你的事情

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");


if (home instanceof HomeFragment && home.isVisible()) {
// do you stuff
}

更多信息 < a href = “ https://github.com/DattaHujare/NavigationDrawer”rel = “ nofollow norefrer”> https://github.com/dattahujare/navigationdrawer 我从未使用 addToBackStack ()处理片段。

executePendingTransactions()commitNow()不工作(

在 androidx (喷气背包)中工作。

private final FragmentManager fragmentManager = getSupportFragmentManager();


public void removeFragment(FragmentTag tag) {
Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
if (fragmentRemove != null) {
fragmentManager.beginTransaction()
.remove(fragmentRemove)
.commit();


// fix by @Ogbe
fragmentManager.popBackStackImmediate(tag.toString(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}