PagerAdapter,FragmentPagerAdapter,FragmentPagerStateAdapter的區別系列——緩存策略

平常大家都能聽到,ViewPager默認緩存三個子項,FragmentPagerAdapter會保存所有的Fragment,子項多時應該盡量不要用它,那么究竟它是怎么保存所有子項的呢?

為了測試三者緩存策略(創建與銷毀子view)的區別,在ViewPager三種Adapter的子view創建和銷毀的方法添加相關的日志代碼,如下:

       @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Log.d("ccc", "destroyItem:" + position);
            //...省略部分代碼
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Log.d("ccc", "instantiateItem:" + position);
            //...省略部分代碼
        }

滑動ViewPager翻頁,觀察控制臺的輸出,三種Adapter針對不同界面、不同滑動方向的翻頁情況打印如下:


ViewPager的三種Adapter的destroyItem和instantiateItem方法調用.png

從圖中我們可以看到,三種Adapter在相同的情況下,ViewPager的子頁面銷毀和創建時機是一樣。但是這好像違背了我們之前學過的,因為我們通常所聽到的都是FragmentPagerAdapter會緩存所有的Fragment子項,而上圖中我們看到的是在滑動的過程中它的destroyItem方法被調用了,而在滑動回來時相對應的子項Fragment也確實調用instantiateItem方法。根本就沒有緩存?!

但是仔細對比了一下三個Adapter創建視圖的過程,發現自己錯了,因為在使用Fragment作為子視圖時,我們是通過getItem方法返回Fragment的,單純從這里打印instantiateItem的調用不代表Fragment真的完全被重新創建了(重新創建代表需要重新add,即從頭走一遍生命周期,但是在這里不能證明),也可以通過兩個FragmentAdapter中instantiateItem的實現證明(觀察getItem方法的調用條件),所以又在Fragment對應的兩種Adapter的getItem中添加相應的log代碼,如下:

        @Override
        public Fragment getItem(int position) {
            Log.d("ccc", "getItem:" + position);
            return fragmentList.get(position);
        }

針對不同情況,控制臺輸出結果如下:


getItem方法的調用.png

通過上圖我們可以看到,FragmentPagerAdapter在最后向右邊劃回來時并沒有調用getItem方法(getItem是創建一個新的Fragment),這也就說明了他沒有重新創建Fragment,證明了它會緩存所有Fragment,那么它到底在哪里做了緩存呢?

百思不得其解,只好去看源碼了,如下:
FragmentPagerAdapter#destroyItem:

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);
    }

以上,我們關注方法的最后一行代碼,在這里,ViewPager的Fragment子項并沒有真正的被移除(FragmentTransaction沒有調用remove方法),FragmentTransaction只是調用了detach方法。detach和remove不同,detach后Fragment的狀態依然保持著,在使用attach()時會再次調用onCreateView()來重繪視圖。而在FragmentStatePagerAdapter的destroyItem方法中是直接調用了remove方法。
這里說明一下detach和remove的區別(結合Fragment的聲明周期說明)


Fragmnet生命周期圖
detach:

對應執行的是Fragment生命周期中onPause()-onDestroyView()的方法,此時并沒有執行onDestroy和onDetach方法。所以在恢復時只需要attach方法即可(可以在FragmentPagerAdapter的instantiateItem方法中看到調用,對應源碼下面給出),attach方法對應的是執行Fragment生命周期中onCreateView()-onResume()。

 @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);
            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;
    }
remove:

比起detach,remove的做法很直接,它直接對應執行了Fragment生命周期中onPause()-onDetach()的方法,所以在恢復(這里需要重新add了)時,需要執行的是add方法(可以在FragmentPagerStateAdapter的instantiateItem方法中看到調用,對應源碼下面給出),add方法對應執行了Fragment生命周期中onAttach()-onResume()的方法。

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        return fragment;
    }
  • 從第一張三種Adapter滑動時打印的日志圖中,我們也了解到:除了滑動到第一個和最后一個子界面,ViewPager始終是緩存三個子界面(這里的緩存指的是ViewPager同一時間會加載三個子view)!
  • 而三種Adapter的緩存策略則各有不同:
    • PagerAdapter:緩存三個,通過重寫instantiateItem和destroyItem達到創建和銷毀view的目的。
    • FragmentPagerAdapter:內部通過FragmentManager來持久化每一個Fragment,在destroyItem方法調用時只是detach對應的Fragment,并沒有真正移除!
    • FragmentPagerStateAdapter:內部通過FragmentManager來管理每一個Fragment,在destroyItem方法 調用時移除對應的Fragment。
  • 所以,我們分情況使用這三個Adapter
    PagerAdapter:當所要展示的視圖比較簡單時適用
    FragmentPagerAdapter:當所要展示的視圖是Fragment,并且數量比較少時適用
    FragmentStatePagerAdapter:當所要展示的視圖是Fragment,并且數量比較多時適用
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容