美文网首页Android开发Android开发经验谈
PagerAdapter,FragmentPagerAdapte

PagerAdapter,FragmentPagerAdapte

作者: android_cyw | 来源:发表于2017-01-17 16:09 被阅读1530次

    平常大家都能听到,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,并且数量比较多时适用

    相关文章

      网友评论

        本文标题:PagerAdapter,FragmentPagerAdapte

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