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针对不同界面、不同滑动方向的翻页情况打印如下:


PagerAdapter,FragmentPagerAdapter,FragmentPagerStateAdapter的区别系列——缓存策略_第1张图片
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);
        }

针对不同情况,控制台输出结果如下:


PagerAdapter,FragmentPagerAdapter,FragmentPagerStateAdapter的区别系列——缓存策略_第2张图片
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的声明周期说明)


PagerAdapter,FragmentPagerAdapter,FragmentPagerStateAdapter的区别系列——缓存策略_第3张图片
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,并且数量比较多时适用

你可能感兴趣的:(PagerAdapter,FragmentPagerAdapter,FragmentPagerStateAdapter的区别系列——缓存策略)