34

我知道这听起来像是FragmentStatePagerAdapter IllegalStateException: <MyFragment> 当前不在 FragmentManager 中的副本, 但他的解决方案与我的情况无关。

我很少遇到以下崩溃:

java.lang.RuntimeException:无法暂停活动 {MyActivity}:

...

引起:java.lang.IllegalStateException: Fragment MyFragment {40648258 id=0x7f070051} 当前不在 android.support.v4.app.FragmentManagerImpl.putFragment(MT:516) 的 FragmentManager 中 android.support.v4.app.FragmentStatePagerAdapter .saveState(MT:185) 在 android.support.v4.view.ViewPager.onSaveInstanceState(MT:881)

...

在 android.view.View.saveHierarchyState(View.java:6238) 在 com.android.internal.policy.impl.PhoneWindow.saveHierarchyState(PhoneWindow.java:1522) 在 android.app.Activity.onSaveInstanceState(Activity.java:1138 ) 在 android.support.v4.app.FragmentActivity.onSaveInstanceState(MT:480) 在 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);
    }
}

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

我找不到任何方法在我的测试设备上重新创建它,只从一些用户那里收到。

我正在使用支持包 v4。

有什么帮助吗?谢谢。

4

12 回答 12

16

FragmentStatePagerAdapter是一段可怕的代码,充满了谷歌承认或不承认的错误,所以我使用这段代码来修复这个特定的崩溃:

    @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();
        }
    }
于 2015-04-16T16:19:39.377 回答
10

当在 ViewPager 上设置 FragmentStatePagerAdapter 时,可能会发生这种情况,并且 Fragment 已填充,但由于在 ViewPager 发生布局之前 Activity 暂停而尚未添加到 FragmentManager。这可以通过从 onCreate 启动另一个 Activity 来可靠地实现,适配器也在 onCreate 中设置。在这种情况下,最好不要设置 Adapter,而是将其设置为 onActivityResult。看到这个问题:https ://code.google.com/p/android/issues/detail?id=77285

我还提交了一个补丁来检查是否在尝试保存状态之前添加了 Fragment。

于 2014-10-13T15:03:31.593 回答
7

我遇到了确切的例外。

就我而言,我有几个 FragmentPagerStateAdapter 管理的片段,但有时片段会切换到位。

我重写 getItemPosition() 并返回新位置并调用 notifyDataSetChanged() 以刷新 ViewPager。一切正常,但有时当我离开 ViewPager 时会发生崩溃。

经过几个小时的深入研究,我发现 FragmentPagerStateAdapter 中有一个错误。

适配器不仅缓存状态,还缓存它认为“活跃”的片段

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

但它不检查片段的位置是否已失效或移动。

这就是它如何得到这个异常:

1.ViewPager和Adapter中有A,B,C三个Fragment。

2.我在适配器中将位置切换到B,A,C并调用notifyDataSetChanged()。

3.ViewPager 按新顺序重新布局片段。但是 mFragments 缓存仍然是 {A,B,C}

4.Swipe几个页面,ViewPager会要求adapter销毁第一个Fragment。然后片段 B,而不是 A,在缓存中设置为空。现在 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();
}

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

于 2014-09-05T05:47:29.823 回答
7

如果您有片段层次结构,请使用getChildFragmentManager()而不是。getFragmentManager()例如。如果你有片段寻呼机。

于 2013-10-12T09:21:47.997 回答
2

如果您的 ViewPager 布局在片段(不是活动)内:

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

于 2014-02-03T18:18:51.880 回答
2

使用以下

final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(f, key);

代替

mFragmentManager.putFragment(state, key, f);

并明确传递捆绑包..

以供参考,

http://developer.android.com/reference/android/app/FragmentTransaction.html#add%28android.app.Fragment,%20java.lang.String%29

于 2013-11-20T11:34:45.530 回答
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);
}}
于 2013-08-31T05:20:31.357 回答
1

您是从自己的代码中调用FragmentStatePagerAdapter.instantiateItem()吗?通常,ViewPager 应该是调用此方法的唯一客户端,但是,很容易遇到依赖此方法的其他限制的变通方法。

由于FragmentStatePagerAdapter工作方式的原因,我猜适配器已经实例化了 Fragment,将其添加到其内部的 Fragment 集合中,甚至开始了一个事务以将该 Fragment 添加到 FragmentManager,但该片段事务尚未提交或执行。这正是instantiateItem()它尚未实例化请求的 Fragment 时所做的事情。

另一种方法FragmentStatePagerAdapter.finishUpdate()是负责提交事务以及执行挂起的事务。 finishUpdate() 必须在 instantiateItem() 之后调用,否则 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() 的实现为空。

于 2013-11-26T06:56:55.100 回答
0

尝试使用

use fragmentTransaction.add()
于 2013-11-14T23:54:12.580 回答
0

你可以尝试使用mFragmentManager.add();

于 2013-07-15T21:12:48.543 回答
0

ViewPager 中的 Fragment 是固定的,而不是尝试替换适配器中的 Fragment,而是尝试给不同的 Fragment 集并更改 notifyDataSet,或者利用 FrameLayout 在视图分页器选项卡的当前 Fragment 上显示另一个 Fragment。

有我的解决方案有效:

在 Fragment 级别与 ViewPager 一起应用的滑动手势,默认滑动禁用

于 2013-11-20T12:46:22.520 回答
0

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

于 2016-12-07T23:05:55.190 回答