Activity恢復時ViewPager中的Fragment恢復錯誤bug

前幾天遇到一個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中沒有再去創建.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容