Android Fragment 回栈的问题
Posted
技术标签:
【中文标题】Android Fragment 回栈的问题【英文标题】:Problems with Android Fragment back stack 【发布时间】:2012-09-13 19:34:13 【问题描述】:我在 android fragment backstack 的工作方式上遇到了一个大问题,如果提供任何帮助,我将不胜感激。
假设你有 3 个片段
[1] [2] [3]
我希望用户能够导航[1] > [2] > [3]
,但在返回的路上(按下返回按钮)[3] > [1]
。
正如我想象的那样,这将通过在创建将片段 [2]
带入 XML 中定义的片段持有者的事务时不调用 addToBackStack(..)
来实现。
事实上,如果我不想在用户按下[3]
上的返回按钮时再次出现[2]
,我就不能在显示片段[3]
的事务中调用addToBackStack
。这似乎完全违反直觉(可能来自 ios 世界)。
无论如何,如果我这样做,当我从[1] > [2]
出发并按回时,我会按预期返回[1]
。
如果我去[1] > [2] > [3]
然后按回我跳回[1]
(如预期的那样)。
现在,当我尝试再次从[1]
跳转到[2]
时,就会发生奇怪的行为。首先,[3]
会在 [2]
出现之前短暂显示。如果此时我按回[3]
,则会显示,如果我再次按回,应用程序将退出。
谁能帮我理解这里发生了什么?
这是我的主要活动的布局 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="vertical" >
<fragment
android:id="@+id/headerFragment"
android:layout_
android:layout_
class="com.fragment_test.FragmentControls" >
<!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
android:id="@+id/detailFragment"
android:layout_
android:layout_
/>
更新 这是我用来通过 nav heirarchy 构建的代码
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]
非常感谢
【问题讨论】:
但片段重叠,当我从片段 C 回压到片段 A 时。 我也有同样的问题,请问如何解决? 参考我的答案。它可能对你有帮助 ***.com/questions/14971780/…> 【参考方案1】:解释:这里发生了什么?
如果我们牢记.replace()
等于我们在文档中知道的.remove().add()
:
替换已添加到容器中的现有片段。这与为所有当前添加的片段调用
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 而不是 fragment 本身!所以现在我们在布局上有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()
方法中:
FragmentTransaction.addToBackStack(String name);
等
【讨论】:
很好的解释@arvis。但是,我们如何才能避免这种行为,而无需求助于像 DexterMoon 中的那种 hacky 方法,或者 Nemanja 中使用 popBackStack 的那种在播放过渡动画时显示 Fragment 的方法? @momo 你可以实现FragmentManager.BackStackChangedListener
来观察后台堆栈的变化。使用onBackStackChanged()
方法监控您的所有交易并根据需要采取行动:例如。跟踪 BackStack 中的事务计数;按名称(FragmentTransaction addToBackStack (String name)
)等检查特定交易。
感谢您的回答。实际上我今天早些时候尝试过,注册了监听器并从 onBackstackChange 中删除了片段。在弹出过渡播放时,片段变成了一个白色的空白区域。我猜这个方法是在弹出动画开始时触发的,而不是在结束时触发的......
避免使用后栈!它对整体效率并没有真正的帮助!每次要导航时使用普通的 replace() 甚至更好地删除/添加!
@Arvis 你能帮我解决同样的问题吗......堆栈计数为 0 但我的片段仍然可见?【参考方案2】:
似乎片段 [3] 在按下返回时并未从视图中移除,因此您必须手动操作!
首先,不要使用replace()
,而是分别使用remove和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();
【讨论】:
这行得通,但这不能在我的项目中使用,因为片段被重新加载!有什么建议吗? 但是,如果我这样做。当我为片段 C 从 C 回到 A 时出现空白屏幕。 我怀疑@Arvis 的回答应该可以帮助您解决问题【参考方案3】:首先感谢@Arvis 大开眼界的解释。
对于这个问题,我更喜欢这里接受的答案的不同解决方案。除了绝对必要的情况外,我不喜欢弄乱覆盖的返回行为,当我尝试自己添加和删除片段时,按下返回按钮时没有默认返回堆栈弹出,我发现自己处于片段地狱:) 如果你.删除时在 f1 上添加 f2 f1 不会调用任何回调方法,如 onResume、onStart 等,这可能非常不幸。
不管怎样,我就是这样做的:
目前展示的只有片段 f1。
f1 -> f2
Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();
这里没有什么特别之处。与片段 f2 相比,此代码将您带到片段 f3。
f2 -> f3
Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
通过阅读文档我不确定这是否可行,据说这种弹出事务方法是异步的,也许更好的方法是调用 popBackStackImmediate()。但据我所知,它在我的设备上运行良好。
上述替代方案是:
final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
这里实际上会在进入 f3 之前短暂地回到 f1,所以那里有一个小故障。
这实际上就是你所要做的,无需覆盖回栈行为...
【讨论】:
但故障,这是个问题 这似乎比监听 BackStack 更改更“hack-ey”。谢谢。【参考方案4】:我知道这是一个老问题,但我遇到了同样的问题并像这样解决它:
首先,使用名称(例如“Frag1”)将 Fragment1 添加到 BackStack:
frag = new Fragment1();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();
然后,每当你想回到 Fragment1(即使在它上面添加 10 个 Fragment 之后),只需使用名称调用 popBackStackImmediate:
getSupportFragmentManager().popBackStackImmediate("Frag1", 0);
希望对某人有所帮助:)
【讨论】:
很好的答案。我不得不使用 getSupportFragmentManager().popBackStackImmediate("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE);让它适用于我的用例 我必须把这段代码放在哪里???? getSupportFragmentManager().popBackStackImmediate("Frag1", 0);在 MainActivty 或 onBackPress 上 它不工作。 :( 这是我的代码,在我的情况下,片段重叠。它打开片段 A,但片段 A 与片段 B 重叠。 FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); fragmentManager.popBackStack(FragmentA.class.getName(),FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();片段E 片段E = 新片段E(); fragmentTransaction.replace(R.id.fragment_content, fragmentE,fragmentE.getClass().getName()); fragmentTransaction.commit();【参考方案5】:@Arvis 回复后,我决定更深入地挖掘,我在这里写了一篇关于此的技术文章:http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/
对于周围的懒惰开发人员。我的解决方案是始终将事务添加到后台堆栈,并在需要时(自动)执行额外的FragmentManager.popBackStackImmediate()
。
代码只有很少的几行代码,在我的示例中,如果用户没有深入后台堆栈(例如从 C 导航到 D )。
因此附加的代码将如下工作 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();
);
【讨论】:
在这一行发生崩溃 fragmentManager.popBackStackImmediate();error: java.lang.IllegalStateException: FragmentManager 已经在 com.example.myapplication.FragmentA$2.onBackStackChanged(FragmentA.java:43) 处执行事务【参考方案6】:如果您正在为 addToBackStack() 和 popBackStack() 苦苦挣扎,那么只需使用
FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`
在 OnBackPressed() 中的 Activity 中按标签找出片段,然后做你的事情
Fragment home = getSupportFragmentManager().findFragmentByTag("Home");
if (home instanceof HomeFragment && home.isVisible())
// do you stuff
更多信息https://github.com/DattaHujare/NavigationDrawer 我从不使用 addToBackStack() 来处理片段。
【讨论】:
【参考方案7】:我认为,当我读到您的故事时,[3] 也处于落后状态。这就解释了为什么你会看到它闪烁。
解决方案是永远不要在堆栈上设置 [3]。
【讨论】:
您好 jdekei 感谢您的意见。问题是我看不到将 [3] 添加到后台堆栈的位置。我添加了另一块代码,它(以编程方式)准确地演示了我使用按钮执行的导航。 它对我也有帮助,但我只使用你代码中的 removeCurFragment 和另一个片段检查器。替换方法对我来说很好,可能是s old method issue but now it
s 很好。谢谢【参考方案8】:
我有一个类似的问题,我在同一个 Activity
[M1.F0]->[M1.F1]->[M1.F2] 中有 3 个连续 片段,然后是一个电话到一个新的Activity
[M2]。如果用户按下 [M2] 中的按钮,我想返回 [M1,F1] 而不是 [M1,F2] ,这就是后按行为已经完成的操作。
为了完成此操作,我删除了 [M1,F2],在 [M1,F1] 上调用 show,提交事务,然后通过使用 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();
嗨 执行此代码后:我无法在按返回键时看到 Fragment2 的值。 我的代码:
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();
【讨论】:
【参考方案9】:executePendingTransactions()
, commitNow()
无效(
曾在 androidx (jetpack) 中工作。
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);
【讨论】:
以上是关于Android Fragment 回栈的问题的主要内容,如果未能解决你的问题,请参考以下文章