IllegalStateException:无法更改Fragment的容器ID

时间:2012-03-28 11:09:14

标签: android android-fragments

Android平台:3.1

我正在尝试将片段从容器A移动到容器B.接下来是完成此操作的代码:

private void reattach(int newContainerId, Fragment frag, String tag) {

      if (frag == null || !frag.isAdded() || (frag.getId() == newContainerId)) { return; }

      final FragmentManager fm = getFragmentManager();
      FragmentTransaction ft = fm.beginTransaction();
      ft.remove(frag);     //stacco il frammento dal container A
      ft.commit();
      fm.executePendingTransactions();

      ft = fm.beginTransaction();
      ft.add(newContainerId, frag, tag); //attacco il frammento sul container D
      ft.commit();
      fm.executePendingTransactions();   
   }

当我运行系统时,我收到以下IllegalStateException:

03-26 00:13:14.829: E/AndroidRuntime(30090): java.lang.RuntimeException: Unable to start activity ComponentInfo{eu.areamobile.apps.sfa/eu.areamobile.apps.sfa.activity.HomeActivity}: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1751)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1767)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3117)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.access$1600(ActivityThread.java:122)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1009)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Looper.loop(Looper.java:132)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.main(ActivityThread.java:4028)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invokeNative(Native Method)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invoke(Method.java:491)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at dalvik.system.NativeStart.main(Native Method)
03-26 00:13:14.829: E/AndroidRuntime(30090): Caused by: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.doAddOp(BackStackRecord.java:338)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.add(BackStackRecord.java:316)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.reattach(HomeActivity.java:340)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:253)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:155)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.onPostCreate(HomeActivity.java:66)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1111)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1734)

经过快速调试后,我注意到在Android 3.1上FragmentTransaction.remove没有将要删除的片段的mContainerId设置为0,而在ICS上它可以正常工作。
有任何建议或解决方法吗?

11 个答案:

答案 0 :(得分:19)

我解决这个问题的方法是在保持状态的同时重新创建片段:

FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.remove(old);
Fragment newInstance = recreateFragment(old);
ft.add(R.id.new_container, newInstance);
ft.commit();

使用以下辅助函数:

private Fragment recreateFragment(Fragment f)
    {
        try {
            Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(f);

            Fragment newInstance = f.getClass().newInstance();
            newInstance.setInitialSavedState(savedState);

            return newInstance;
        }
        catch (Exception e) // InstantiationException, IllegalAccessException
        {
            throw new RuntimeException("Cannot reinstantiate fragment " + f.getClass().getName(), e);
        }
    }

它适用于我,至少使用最新的支持库(r11),虽然我还没有测试过多。

额外的成本是将片段实例化两次。

答案 1 :(得分:16)

在尝试了类似问题的所有答案之后,看起来我已经找到了解决问题的方法。

第一个问题 - 在尝试将片段添加到另一个容器之前,您必须提交执行删除事务。感谢您访问nave's answer

但这并不是每次都有用。第二个问题是后台堆栈。它以某种方式阻止了交易。

所以对我有用的完整代码如下:

manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
manager.beginTransaction().remove(detailFragment).commit();
manager.executePendingTransactions();
manager.beginTransaction()
    .replace(R.id.content, masterFragment, masterTag)
    .add(R.id.detail, detailFragment, activeTag)
    .commit();              

答案 2 :(得分:2)

我有类似的问题,并且在第二次添加片段之前调用manager.executePendingTransactions()为我做了诀窍。 谢谢!

答案 3 :(得分:1)

尝试多次添加相同的fragment时可能会发生这种情况。

    public void populateFragments() {
        Fragment fragment = new Fragment();
        //fragment is added for the first time
        addFragment(fragment);

        // fragment is added for the second time
        // This call will be responsible for the issue. The 
        addFragment(fragment);
    }

    public void addFragment(Fragment fragment) {
        FrameLayout frameLayout = AppView.createFrameLayout(context);
        view.addView(frameLayout);
        getSupportFragmentManager().beginTransaction().add(frameLayout.getId(), fragment).commit();
    }

答案 4 :(得分:0)

基于Fragment Developer Guide,它声明:

  

如果在执行删除片段的事务时未调用addToBackStack(),则在提交事务并且用户无法导航回到该片段时,该片段将被销毁。然而,如果您在删除片段时调用addToBackStack(),则片段将停止,并且如果用户导航回来将恢复该片段

所以我假设在调用

之后你的frag对象被破坏了
ft.remove(frag);     //stacco il frammento dal container A
ft.commit();

但你说它在ICS中有效,这很奇怪。为什么不试试replace方法,看看会发生什么?

希望这会有所帮助......

答案 5 :(得分:0)

尝试使用replace()方法。如果不使用提交两次。

答案 6 :(得分:0)

如果这没有帮助,我需要稍后原型化,但我会尝试的一件事就是不做单独的事务,因为即使你调用executePendingTransactions()它也不应该是一个阻塞调用,所以虽然它是做其余的代码将开始执行,包括尝试将Fragment添加到新容器,而它可能仍然存在于当前容器中。

由于您是通过单个方法完成所有这些操作,因此您可以尝试执行删除并添加一个FragmentTransaction,以便您知道它将按照您希望的顺序执行,以便您知道从一个容器中删除Fragment并将其添加到新容器然后提交时。另外,如果你真的想把它作为两个单独的事务来做,那么基础Fragment类有一个isRemoving()方法,它会告诉你是否从它的容器中删除了Fragment,你可以等到它变为false,这样你就知道它了被删除了。也就是说,如果现在一个事务在执行之前依赖于另一个事务,那么你就开始引入同步问题了,我认为没有任何理由不去删除并添加为单个事务。

谢谢, 大卫

答案 7 :(得分:0)

除了糟糕的编程原则之外,是否有任何正当理由,您是否希望在一个或两个活动中保留对现有片段的引用?而不是保存片段的状态,只是重新创建它?

我使用以下方法创建片段:

    public static ContactsFragment mContactsFragment;

    public static ContactsFragment getInstance() {
    if (mContactsFragment == null) {
        mContactsFragment = new ContactsFragment();
    }
    return mContactsFragment;
}

自从我学习了Android以来,我已经完成了这项工作,但我不知道为什么我想要静态地引用我的片段,尽管它存在于每个人的例子中。最初我认为是静态引用Context,但是你总是可以在创建一个新片段时设置它,并且无论如何保存一个上下文通常会导致奇怪的错误。

为什么不能

    public static ContactsFragment newInstance(Context c) {
    ContactsFragment mContactsFragment = new ContactsFragment();
    mContext = c;
    return mContactsFragment;
}

只需保存您对片段所做的任何工作,并在需要时重新创建它? 也许我可以想象如果你创造了100多个(新闻应用程序?),你可能不想每次创建它们,而是将它们存储在内存中?任何人吗?

答案 8 :(得分:0)

所以,我有同样的问题,并以不同的方式(至少一点点)解决了问题。我的应用程序有4个片段,我需要在设备处于横向模式时进行管理。

让我解释一下我的应用和解决方案:

我的应用  我的应用程序中有4个片段: 1 - 食谱(包含食谱列表) 2 - 成分(选择配方的成分列表) 3 - 步骤(步骤列表) 4 - 播放器(一个叫做播放视频的片段)

在纵向模式下我没有问题,一切正常!

<强>风景 我的活动有两个容器用于我的碎片,一个在左侧,包含我的食谱列表,另一个在右侧。右侧的这个容器将会发生变化。

我有这个问题,在肖像模式下,我的片段正在显示,当我旋转我的设备时,我想要显示的相同片段转到详细信息容器。 这就是我所有问题的开始。

我的解决方案

  1. 保存变量中显示的最后一个片段;
  2. 保存状态片段onSavedInstance;
  3. 在onCreate中检索片段,显示最后一个片段。
  4. 所以,更具体的是我创造了这个要点来帮助 https://gist.github.com/jillesRagonha/4c184165b92fdf026ab3cd033b64b1bf

    希望这有助于任何人:D

答案 9 :(得分:0)

我发现问题是我需要两个FragmentTransactions。我试图在同一个目录上两次执行remove()和commit()。

 // create new fragment
 MyFragment myNewFragment = MyFragment.newInstance(this, data, true);
 FragmentManager fm = this.getSupportFragmentManager();
 // clear the back stack
 fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

 //fragmentTransaction is created for the removal of the old
 FragmentTransaction fragmentTransaction = fm.beginTransaction();
 fragmentTransaction.remove(myOldFragment);
 fragmentTransaction.commit();
 fm.executePendingTransactions();

 // fragmentTransaction2 is created to add the new one
 FragmentTransaction fragmentTransaction2 = fm.beginTransaction();
 fragmentTransaction2.add(R.id.fragment_frame, myNewFragment);
 fragmentTransaction2.commit();
 fm.executePendingTransactions();

答案 10 :(得分:0)

删除 transaction.commitAllowingStateLoss();(如果存在)(对于之前的片段)并使用 transaction.commit(); 代替。

最好为 BaseActivity/BaseFragment 中的所有片段/活动保留一个像这样的全局函数

protected void replaceMyFragment(Fragment fragment, int container_view_id, boolean addToBackStack, String tag) {
    // Begin the transaction
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    // Replace the contents of the container with the new fragment
    //ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
    ft.replace(container_view_id, fragment, tag);
    // or ft.add(R.id.your_placeholder, new FooFragment());
    if (addToBackStack) {
        ft.addToBackStack(tag);
    }
    // Complete the changes added above
    ft.commit();
}

然后这样称呼它们:

replaceMyFragment(new YourFragment(),R.id.your_container,false,fragment.getTag());

在您的活动/片段中。