美文网首页Android开发学习
Activity恢复时ViewPager中的Fragment恢复

Activity恢复时ViewPager中的Fragment恢复

作者: 朔野 | 来源:发表于2016-09-25 17:13 被阅读2216次

前几天遇到一个bug,是activity被回收后在恢复时一个ViewPager中的fragment状态错误引起的 ,花了些时间解决,记录一下:

现象

Activity A中有fragment B, B中有个ViewPager V,V的adapter设置的是FragmentPagerAdapter,也就是说fragment B有几个childFragment. 在手机内存紧张时A被回收,但在A恢复时,B中的ViewPager V没有显示,而且页面下拉刷新应用crash(下拉刷新时用到了V的adapter的实例)

原因

通过查看源码,发现Fragment会在被回收时保存ChildFragmentManager的状态:如下

//fragmentactivity
@Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState(); //最终调用mFragmentManager.saveAllState()
      ...
      ...
    }
//FragmentManager
Parcelable saveAllState() {
       ...
 
                if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                    fs.mSavedFragmentState = saveFragmentBasicState(f);  //会调用到fragment的performSaveInstanceState
       ...
    }
//fragment
  void performSaveInstanceState(Bundle outState) {
        onSaveInstanceState(outState);
        if (mChildFragmentManager != null) {
            Parcelable p = mChildFragmentManager.saveAllState();
            if (p != null) {
                outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p);
            }
        }
    }

可以看到fragment的childfragmentmanager也会被记录下来,也就是说ChildFragment会被记录下来,恢复的时候也会恢复出来.
再来看看viewpager的状态保存相关代码:

//viewpager
 @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.position = mCurItem;
        if (mAdapter != null) {
            ss.adapterState = mAdapter.saveState();
        }
        return ss;
    }
//PagerAdapter
 public Parcelable saveState() {
        return null;
    }

可见pageradapter并不会保存状态,而子类fragmentpageradapter默认也没有保存状态
所以当activity A恢复状态时,fragment B恢复了,viewpager中的几个fragment也恢复了,但是 viewpager的adapter却没有恢复,于是按我之前的逻辑就会重新创建一个fragmentpageradapter,而这时候我的做法是重新创建了几个fragment的实例传给了fragmentpageradapter,再来看看fragmentpageradapter显示fragment的相关代码

 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position); //1
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

可以发现由于Fragment B已经通过恢复childFragmentManager恢复了viewpager中的几个fragment,所以上面的代码不会走到1处,也就是说显示的fragment是从回收状态恢复时childfragmentmanager创建的,而不是我们自己创建的, 所以问题就是这个.
那怎么解决这个问题呢?
我的办法是在创建fragmentpageradapter时判断Fragment B有没有viewpager中的几个fragment实例,如果有就将其传给fragmentpageradapter,否则再创建fragment的实例:

List<Fragment> fragments = getChildFragmentManager().getFragments();
 ArrayList<HomeListFragment> list = new ArrayList<>();
        if (fragments == null || fragments.isEmpty()) {
            list.add(HomeListFragment.newInstance("1"));
            list.add(HomeListFragment.newInstance("2"));
            list.add(HomeListFragment.newInstance("3"));
            list.add(HomeListFragment.newInstance("4"));
        } else {
            for (Fragment fragment : fragments) {
                if (fragment instanceof HomeListFragment) {
                    list.add((HomeListFragment) fragment);
                }
            }
        }

childFragmentManager在保存状态和恢复状态时会保留顺序,所以不用担心恢复后的顺序和viewpager中的顺序不对。

总结

其实说了这么多,关键就是一条:fragment的获取一定要通过fragmentmanager .可以用findFragmentWithTag或者getFragments来得到特定或者所有fragment。如果没有获取到fragment实例再去创建。如果通过是ViewPager来间接控制Fragment更要注意,传给FragmentPagerAdapter的Fragment实例一定要和FragmentManager中的一致,如果FragmentManager中没有再去创建.

相关文章

网友评论

    本文标题:Activity恢复时ViewPager中的Fragment恢复

    本文链接:https://www.haomeiwen.com/subject/jtyfyttx.html