【问题标题】:IllegalStateException when adding Fragment from Backstack从 Backstack 添加 Fragment 时出现 IllegalStateException
【发布时间】:2019-07-10 14:57:45
【问题描述】:

我有时会收到以下崩溃报告:

java.lang.IllegalStateException: 
  at androidx.fragment.app.FragmentManagerImpl.addFragment (FragmentManagerImpl.java:1916)
  at androidx.fragment.app.BackStackRecord.executePopOps (BackStackRecord.java:828)
  at androidx.fragment.app.FragmentManagerImpl.executeOps (FragmentManagerImpl.java:2622)
  at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether (FragmentManagerImpl.java:2411)
  at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute (FragmentManagerImpl.java:2366)
  at androidx.fragment.app.FragmentManagerImpl.execPendingActions (FragmentManagerImpl.java:2273)
  at androidx.fragment.app.FragmentManagerImpl$1.run (FragmentManagerImpl.java:733)
  at android.os.Handler.handleCallback (Handler.java:808)
  at android.os.Handler.dispatchMessage (Handler.java:101)
  at android.os.Looper.loop (Looper.java:166)
  at android.app.ActivityThread.main (ActivityThread.java:7529)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:245)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:921)

在 FragmentManagerImpl 的第 1916 行我可以找到:

throw new IllegalStateException("Fragment already added: " + fragment);

所以它说已经添加了一些片段。不幸的是,谷歌不再在谷歌播放控制台中显示消息(已经添加了哪个片段)。据我了解 Stacktrace 从后台堆栈添加片段时会发生此异常吗?

我有一个FrameLayout,我在其中添加/删除片段。我总是用以下代码添加它们:

public void addFragment(FragmentActivity activity, Fragment fragment) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    String tag = fragment.getClass().getCanonicalName();
    Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(tag);
    if (fragment.isAdded()) {
        return;
    }
    if (prev != null) {
        transaction.remove(prev);
    }
    transaction.replace(R.id.fragment_container, fragment, tag);
    transaction.addToBackStack(null);
    transaction.commit();

我使用以下方法添加 DialogFragments:

public void openFragmentDialog(FragmentActivity activity, DialogFragment dialogFragment, String tag) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(tag);
    if (prev != null) {
        transaction.remove(prev);
    }
    transaction.addToBackStack(null);
    dialogFragment.show(transaction, tag);
}

IllegalStateException 会在什么情况下发生?我是否以错误的方式添加/替换 Fragments/DialogFragments?我永远无法重现该错误。但我收到了来自 Android 4.4 - Android 9 和所有类型设备的报告,但我不知道它会发生在哪里。

可能是带有动画或慢速设备的东西吗?因为它只是偶尔发生。

【问题讨论】:

标签: android android-fragments android-dialogfragment illegalstateexception fragment-backstack


【解决方案1】:

注意,您正在添加和删除具有相同标签的两个不同片段——标签也应该是不同的。如果prev片段是一个不同类的实例,为什么要用fragment的类名作为标签来查找呢?

您应该添加一个if 语句来检查prevfragment 有时是否不是同一个实例(因为相同的标签)。

如果这两个片段是同一个实例,并且您仍想删除然后添加相同的片段 - 您可能必须在两个不同的事务中执行此操作以防止出现"Fragment already added" 异常。

【讨论】:

  • 好的,当它们是同一个实例时(什么意思,prev!= null)我可以做类似if (prev.isVisible()) return; else // Create new transaction to remove prev first的事情?因此,作为结论,我应该将transaction.remove(prev) 替换为“创建新事务并删除prev”对吗?
  • 我认为理想情况是prevfragment 不是同一个实例——这可以通过为每个实例使用不同的tags 来轻松实现,例如String tag = fragment.getClass().getCanonicalName() + fragment.hashCode(); 在这种情况下,您只需要跟踪唯一标签,以便稍后删除添加的片段。
  • 我的意思是prev == fragment。如果它们是同一个实例——那么甚至有必要添加fragment,因为它已经添加/存在。
  • 如果它们是同一个实例并且您仍想删除然后重新添加 fragment 那么我建议使用 2 个事务:activity.getSupportFragmentManager().beginTransaction().remove(fragment).commit(); activity.getSupportFragmentManager().beginTransaction().add(fragment).commit();
  • @jhavatar,我认为,事务是异步的,所以第二个可以在第一个完成之前开始。所以,我在第一个事务中使用commitNow()(在第二个事务中使用replace 而不是add)。 fragment 在两个事务中是否相同?
【解决方案2】:

tag 必须是唯一的。如果可能,将其替换为 null,否则替换为独特的。

public void addFragment(FragmentActivity activity, Fragment fragment) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.fragment_container, fragment);
    transaction.addToBackStack(null);
    transaction.commit();
}

【讨论】:

  • 如果添加/显示,每个片段/对话片段都有一个唯一的标签。
  • 我说的是同一个片段的两个实例
  • 我希望永远不会发生相同片段的两个实例。有两个实例怎么会发生?
  • 我不确定你是如何实现它的。如果您使用的是底部导航,您可以按片段 A、片段 B 和片段 A,而无需按两次返回按钮
  • 我认为这在我的应用程序中是不可能的。您一次只能看到一个片段,并且可以使用按钮和后退按钮来回导航。但我会再考虑一下。
【解决方案3】:

这是我为检查和防止重复片段所做的工作:

MyFragment myFragment = (MyFragment) fragmentManager.findFragmentByTag(MY_FRAGMENT_TAG);

if(myFragment != null && myFragment.isVisible()){
    // Return early if the fragment already exists & is visible
    return;

}else if(myFragment == null){
    // Create a new instance of the fragment if none already exist
    myFragment = new MyFragment();
}

// Perform the fragment transaction
fragmentManager.beginTransaction()
    .replace(R.id.content_container, myFragment, MY_FRAGMENT_TAG)
    .addToBackStack(MY_FRAGMENT_TAG) // Pass in the tag if you want to add to the back stack
    .commit();

对于对话框片段,我只需创建一个新实例并在其上调用.show()。该对话框应通过与之交互来解除:

MyDialog myDialog = new MyDialog();

// I call getChildFragmentManager here b/c I was using this from within a fragment
// but you can get whichever Fragment Manager is appropriate
myDialog.show(getChildFragmentManager(), MY_DIALOG_TAG);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    • 2013-08-20
    • 1970-01-01
    • 2014-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多