FragmentPagerAdapter和FragmentStatePagerAdapter分析

2,530 阅读6分钟

ViewPager和Fragment结合使用时,提供了两种PagerAdapter实现。 FragmentPagerAdapter和FragmentStatePagerAdapter。

一、我们先来看下Google对其注释。

FragmentPagerAdapter
/**
 * Implementation of {@link PagerAdapter} that
 * represents each page as a {@link Fragment} that is persistently
 * kept in the fragment manager as long as the user can return to the page.
 *
 * <p>This version of the pager is best for use when there are a handful of
 * typically more static fragments to be paged through, such as a set of tabs.
 * The fragment of each page the user visits will be kept in memory, though its
 * view hierarchy may be destroyed when not visible.  This can result in using
 * a significant amount of memory since fragment instances can hold on to an
 * arbitrary amount of state.  For larger sets of pages, consider
 * {@link FragmentStatePagerAdapter}.
 */
 /**
  * PagerAdapter的具体实现,每个页面(Fragment)都持久保存在Fragment Manager中,以便用户返回
  * 最好用在少数静态Fragments的场景,用户访问过的Fragment都会缓存在内存中,即使其视图层次不可见而被释放(onDestroyView)
  * 因为Fragment可能保存大量状态,因此这可能会导致使用大量内存。
  * 页面很多时,可以考虑FragmentStatePagerAdapter
  */
FragmentStatePagerAdapter
/**
 * Implementation of {@link PagerAdapter} that
 * uses a {@link Fragment} to manage each page. This class also handles
 * saving and restoring of fragment's state.
 *
 * <p>This version of the pager is more useful when there are a large number
 * of pages, working more like a list view.  When pages are not visible to
 * the user, their entire fragment may be destroyed, only keeping the saved
 * state of that fragment.  This allows the pager to hold on to much less
 * memory associated with each visited page as compared to
 * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
 * switching between pages.
 */
 /**
  * PagerAdapter的具体实现,也处理保存和恢复Fragment的状态数据
  * 这个版本在大量页面时更有用,比如列表。
  * 当页面不可见时,整个Fragment可能会被销毁(onDestroy),只保存Fragment的状态
  * 可以持有少的内存来达到切换更多页面的场景
  *
  */

二、Adapter的初始化和销毁

下面通过分析源码,来验证下,是如何实现上面Google的注释的。

1. 初始化

ViewPager初始化时,会调用PagerAdapter的instantiateItem方法。

FragmentPagerAdapter
public Object instantiateItem(@NonNull 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);
        // 直接从FragmentManager中获取,即从缓存中获取
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            // 调用getItem创建新的Fragment
            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;
    }
FragmentStatePagerAdapter
public Object instantiateItem(@NonNull 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);
        // 恢复保存的State数据
        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;
    }

2. 销毁

FragmentPagerAdapter
@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull 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);
    }
只调用了detach方法
FragmentStatePagerAdapter
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
    }
缓存State
清除Fragment列表当前位置数据
调用remove删除Fragment

3. Fragment生命周期

FragmentPagerAdapter
1. 初始化运行 初始化2个页面。(当前缓存页面01)
D/FragmentList: getItem 0
D/FragmentList: getItem 1
D/FragmentList: 0onAttach
D/FragmentList: 0onCreate null
D/FragmentList: 1onAttach
D/FragmentList: 1onCreate null
D/FragmentList: 0onCreateView
D/FragmentList: 0onActivityCreated
D/FragmentList: 1onCreateView
D/FragmentList: 1onActivityCreated

2. 向右滑动1页 从缓存加载页面1,创建页面2(当前缓存0123个页面)
D/FragmentList: getItem 2
D/FragmentList: 2onAttach
D/FragmentList: 2onCreate null
D/FragmentList: 2onCreateView
D/FragmentList: 2onActivityCreated

3. 向右滑动1页 从缓存加载页面2,创建页面3,删除页面0的View(当前缓存01234个页面)
D/FragmentList: getItem 3
D/FragmentList: 3onAttach
D/FragmentList: 3onCreate null
D/FragmentList: 0onDestroyView
D/FragmentList: 3onCreateView
D/FragmentList: 3onActivityCreated

4. 向左滑动 从缓存加载页面1,创建页面0的View,删除页面3的View(当前缓存01234个页面)
D/FragmentList: 0onCreateView
D/FragmentList: 0onActivityCreated
D/FragmentList: 3onDestroyView
FragmentStatePagerAdapter
1. 初始化运行 初始化2个页面。(当前缓存页面01)
D/FragmentList: getItem 0
D/FragmentList: getItem 1
D/FragmentList: 0onAttach
D/FragmentList: 0onCreate null
D/FragmentList: 1onAttach
D/FragmentList: 1onCreate null
D/FragmentList: 0onCreateView
D/FragmentList: 0onActivityCreated
D/FragmentList: 1onCreateView
D/FragmentList: 1onActivityCreated

2. 向右滑动1页 从缓存加载页面1,创建页面2(当前缓存0123个页面)
D/FragmentList: getItem 2
D/FragmentList: 2onAttach
D/FragmentList: 2onCreate null
D/FragmentList: 2onCreateView
D/FragmentList: 2onActivityCreated

3. 向右滑动1页 从缓存加载页面2,创建页面3,保存页面0的State并销毁(当前缓存1233个页面)
D/FragmentList: 0onSaveInstanceState Bundle[{}]
D/FragmentList: getItem 3
D/FragmentList: 3onAttach
D/FragmentList: 3onCreate null
D/FragmentList: 0onDestroyView
D/FragmentList: 0onDestroy
D/FragmentList: 0onDetach
D/FragmentList: 3onCreateView
D/FragmentList: 3onActivityCreated

4. 向左滑动 从缓存加载页面1,创建页面0,保存页面3的state并销毁(当前缓存0123个页面)
D/FragmentList: getItem 0
D/FragmentList: 3onSaveInstanceState Bundle[{}]
D/FragmentList: 0onAttach
D/FragmentList: 0onCreate Bundle[{android:user_visible_hint=false, android:view_state={16908292=android.view.AbsSavedState$1@6718494, 16908298=AbsListView.SavedState{332bb3d selectedId=-9223372036854775808 firstId=-1 viewTop=0 position=0 height=1570 filter=null checkState=null}, 2131230892=android.view.AbsSavedState$1@6718494}}]
D/FragmentList: 3onDestroyView
D/FragmentList: 3onDestroy
D/FragmentList: 3onDetach
D/FragmentList: 0onCreateView
D/FragmentList: 0onActivityCreated

三、setOffscreenPageLimit

关于第一次加载,默认加载Fragment的个数问题,还需要研究下setOffscreenPageLimit方法的处理。

默认和最小值是DEFAULT_OFFSCREEN_PAGES=1。 表示,当前页面,左右侧,需要创建的Fragment的个数。

所以最大创建的个数为 2(左右侧) * mOffscreenPageLimit + 1(当前可见页面)

四、区别总结

  1. FragmentPagerAdapter 中所有创建过的Fragment都缓存在FragmentManager中;页面切换,只是调用detach,而不是remove,所以只执行onDestroyView,而不是onDestroy。
  2. FragmentStatePagerAdapter缓存的个数是(mOffscreenPageLimit * 2 + 1),而不是全部;页面切换,调用remove删除,且要缓存State。

五、Fragment的setUserVisibleHint,实现懒加载

通过上面的分析,我们知道,ViewPager默认会加载mOffscreenPageLimit个页面,并执行onAttach->onCreate->onCreateView->onActivityCreate->onResume。

所以如果在上面的生命周期中处理页面逻辑(比如网络请求),那么可能在页面不可见时就进行了处理。

因此,需要判断Fragment的可见,在可见时加载。就用到setUserVisibleHint。

不过该方法会先于onAttach之前,所以具体实现时需要判断下其他生命周期情况。

注意:setUserVisibleHint是在PagerAdapter类中初始化和滚动时调用的,所以需要配合ViewPager才能使用。