非法状态异常: <MyFragment> 当前未在 FragmentManager 中

35

我知道这听起来像是FragmentStatePagerAdapter IllegalStateException: <MyFragment> is not currently in the FragmentManager的重复,但他提供的解决方案对我的情况不适用。

我偶尔会遇到以下崩溃:

java.lang.RuntimeException: Unable to pause activity {MyActivity}:

...

Caused by: java.lang.IllegalStateException: Fragment MyFragment {40648258 id=0x7f070051} is not currently in the FragmentManager at android.support.v4.app.FragmentManagerImpl.putFragment(MT:516) at android.support.v4.app.FragmentStatePagerAdapter.saveState(MT:185) at android.support.v4.view.ViewPager.onSaveInstanceState(MT:881)

...

at android.view.View.saveHierarchyState(View.java:6238) at com.android.internal.policy.impl.PhoneWindow.saveHierarchyState(PhoneWindow.java:1522) at android.app.Activity.onSaveInstanceState(Activity.java:1138) at android.support.v4.app.FragmentActivity.onSaveInstanceState(MT:480) at MyActivity.onSaveInstanceState(MT:336)

看起来这是我无法理解的来自FragmentStatePagerAdapter的奇怪代码:

for (int i=0; i<mFragments.size(); i++) {
    Fragment f = mFragments.get(i);
    if (f != null) {
        if (state == null) {
            state = new Bundle();
        }
        String key = "f" + i;
        mFragmentManager.putFragment(state, key, f);
    }
}

看起来适配器可以从 mFragments获取我的 Fragment,但无法将其状态添加到 FragmentManager

我在我的测试设备上找不到任何重现此问题的方法,只是从一些用户那里收到了此问题。

我正在使用 support package v4。

有什么帮助吗? 谢谢。


2
请查看这个这个 - Alejandro Colorado
1
我遇到了类似的错误,而Alejandro Colorado建议的链接解决了它。经过初步查看支持库源代码,看起来Fragment状态是“未激活”的(mIndex < 0)。由于这个错误的完全随机性(我无法重现它),我认为它表达了一个深深扎根于Fragment/FragmentManager代码中的问题... - Rick77
12个回答

18

FragmentStatePagerAdapter 是一个充满着 bug 的可怕代码,不管 Google 是否承认,我使用这段代码来修复特定的崩溃:

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // Yet another bug in FragmentStatePagerAdapter that destroyItem is called on fragment that hasnt been added. Need to catch
        try {
            super.destroyItem(container, position, object);
        } catch (IllegalStateException ex) {
            ex.printStackTrace();
        }
    }

我也遇到了这个问题。 - RevanthKrishnaKumar V.
最终我们也选择了这个快速方法,我们的错误也在destroyItem方法中。 - Henrique de Sousa

11
当一个FragmentStatePagerAdapter被设置在ViewPager上,而Fragments正在被填充但由于Activity在ViewPager的布局完成之前暂停,因此尚未添加到FragmentManager时,就会发生这种情况。通过从onCreate启动另一个Activity并且在onCreate中也设置了Adapter来可靠地遇到这种情况。在这种情况下,最好等到onActivityResult后再设置Adapter。请参见此问题:https://code.google.com/p/android/issues/detail?id=77285 我还提交了一个修补程序以检查是否已添加Fragment,然后再尝试保存状态。

1
谢谢,这听起来像是一个有前途的线索。我会去看看的。 - marmor
@marmor 您可以在您的适配器实现中重写 saveState 方法,如果 FragmentManager 没有 Fragments,则跳过调用 super,或者将 super.saveState 包装在 try/catch 中以捕获 IllegalStateException 异常。 - piusvelte
这是解决方案吗?因为补丁仍未包含在内。 - divyenduz

8
我遇到了完全相同的异常。
在我的情况下,我有几个由FragmentPagerStateAdapter管理的片段,但有时片段会在位置上切换。
我重写getItemPosition()并返回新的位置并调用notifyDataSetChanged()来刷新ViewPager。一切都很好,但有时当我离开ViewPager时会发生崩溃。
经过几个小时的挖掘,我发现FragmentPagerStateAdapter中存在一个错误。
该适配器不仅缓存状态,还缓存它认为“活动”的片段。
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

但它不会检查片段的位置是否无效或移动。
这就是它出现异常的原因:
1.我在ViewPager和适配器中有A、B、C三个片段。
2.我将位置从适配器中的B、A、C切换,并调用notifyDataSetChanged()。
3.ViewPager通过新顺序重新布局片段。但mFragments缓存仍为{A,B,C}。
4.滑动几页,ViewPager将要求适配器销毁第一个片段。然后,片段B(而不是A)在缓存中被设置为null。现在,mFragments是{null,A,C}。
5.离开ViewPager,触发onSaveInstanceState。mFragments中的所有片段都被认为是活动的,并调用putFragment(),直到FragmentManagerImpl发现A不在其中并抛出异常。
所以这是我的不太温和的解决方案:
1.复制整个FragmentPagerStateAdapter源代码。
2.重写notifyDataSetChanged()方法以按以下方式重新排列缓存。
@Override
public void notifyDataSetChanged() {
  List<Fragment> oldFragments = new ArrayList<>(mFragments);
  List<Fragment.SavedState> oldStates = new ArrayList<>(mSavedState);
  for (int i = 0; i < getCount(); i++) {
    if (i < mFragments.size()) {
      Fragment f = mFragments.get(i);
      if (f != null) {
        int newPosition = getItemPosition(f);
        if (newPosition == POSITION_UNCHANGED || newPosition == i) {
        } else if (newPosition == POSITION_NONE) {
          if (i < mSavedState.size()) {
            mSavedState.set(i, null);
          }
          mFragments.set(i, null);
        } else {
          while (i >= mFragments.size()) {
            mFragments.add(null);
          }
          if (oldStates.size() > i) {
            mSavedState.set(newPosition, oldStates.get(i));
          }
          mFragments.set(newPosition, oldFragments.get(i));
        }
      } else {
        /*
         * No Fragment but that's possible there's savedState and position has
         * changed.
         */
      }
    }
  }
  super.notifyDataSetChanged();
}

希望这对你们中的一些人有用!

我建议在Android问题跟踪器上开启一个错误报告,既然您已经有了解决方案,您甚至可以直接将其提交到Android Gerrit审查系统:https://android-review.googlesource.com/。 - Joe

8
如果您有片段层次结构,例如如果您有片段页面,则请使用getChildFragmentManager()而不是getFragmentManager()

1
谢谢,但我不使用片段层次结构,只在一个活动下使用3个片段。 - marmor
我正在使用一个包含有ViewPager管理Fragment的Activity和Fragment。所以改用getChildFragmentManager()解决了我的问题。 - nsL
如何使用 ActionBarActivity 完成这个操作?我只有 getSupportFragmentManager() - Jonas Borggren
当我们使用普通活动时,无法访问getChildFragmentManager。 - Renan Nery

2

谢谢您的回答,但问题中的代码来自Android的FragmentStatePagerAdapter类,而不是我的软件包。请重新阅读我的问题。 - marmor
FragmentStatePagerAdapter 用于维护片段的状态。 - Sripathi
它用于显示多个片段并排,允许用户在它们之间滑动,同时保持状态。我正在使用这个类来展示一些片段,但很少会出现上述崩溃。 - marmor
就像这个案例一样,当我将一个MapFragment添加到FragmentManger时,我遇到了IllegalArgumentException异常,即使我已经删除了之前添加的MapFragment。它显示父级有一个具有相同ID的子级。我已经实现了try catch,因为我找不到其他解决方法。但是我正在分析这个问题。我会回来给你提供确切的解决方案。 - Sripathi

2
如果您的ViewPager位于一个Fragment中(而不是Activity):

mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getChildFragmentManager()));

1
在 Activity 的 onCreate() 方法中编写以下内容:
pager = (ViewPager) findViewById(R.id.pager);
    adapter = new SwipePagerAdapter(getSupportFragmentManager());
    pageOneFragment = new PageOneFragment();
    adapter.addFragment(pageOneFragment);

适配器代码:

public class SwipePagerAdapter extends FragmentStatePagerAdapter
{
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public SwipePagerAdapter(FragmentManager fm)
{
    super(fm);
}

@Override
public Fragment getItem(int position)
{
    return mFragments.get(position);
}

@Override
public int getCount()
{
    return mFragments.size();
}

public void addFragment(Fragment fragment)
{
    mFragments.add(fragment);
    notifyDataSetChanged();
}

@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
    super.destroyItem(container, position, object);
}

@Override
public CharSequence getPageTitle(int position)
{
    return super.getPageTitle(position);
}}

1
你是否正在从自己的代码中调用FragmentStatePagerAdapter.instantiateItem()方法?一般来说,ViewPager应该是唯一调用此方法的客户端,但是,很容易遇到依赖于此方法的其他限制的解决方法。
由于FragmentStatePagerAdapter的工作方式,我猜测适配器已经实例化了Fragment,将其添加到内部Fragment集合中,甚至开始了一个事务,将该Fragment添加到FragmentManager中,但是该Fragment事务尚未提交或执行。这正是instantiateItem()在没有实例化所请求的Fragment时所做的操作。
另一个方法FragmentStatePagerAdapter.finishUpdate()负责提交事务以及执行挂起的事务。必须在instantiateItem()之后调用finishUpdate(),否则Fragment可能不会被添加到FragmentManager中。
可以尝试像这样做:
// Get the current Fragment presented by the ViewPager.
pagerAdapter.startUpdate(viewPager);
Fragment fragment = pagerAdapter.instantiateItem(viewPager, viewPager.getCurrentItem());
pagerAdapter.finishUpdate(viewPager);
return fragment;

自API 19起,startUpdate()的实现为空。


0

尝试使用

use fragmentTransaction.add()

5
请进一步阐述您的答案,使其更易于理解和使用,但不要改变原意。 - Szymon

0

请仔细检查您的活动是否已经实现了onSaveInstanceStateonRestoreInstanceState方法,并验证它们是否正确地保存和加载了片段的状态。


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