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个页面。(当前缓存页面0、1)
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(当前缓存0、1、2共3个页面)
D/FragmentList: getItem 2
D/FragmentList: 2onAttach
D/FragmentList: 2onCreate null
D/FragmentList: 2onCreateView
D/FragmentList: 2onActivityCreated
3. 向右滑动1页 从缓存加载页面2,创建页面3,删除页面0的View(当前缓存0、1、2、3共4个页面)
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(当前缓存0、1、2、3共4个页面)
D/FragmentList: 0onCreateView
D/FragmentList: 0onActivityCreated
D/FragmentList: 3onDestroyView
FragmentStatePagerAdapter
1. 初始化运行 初始化2个页面。(当前缓存页面0、1)
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(当前缓存0、1、2共3个页面)
D/FragmentList: getItem 2
D/FragmentList: 2onAttach
D/FragmentList: 2onCreate null
D/FragmentList: 2onCreateView
D/FragmentList: 2onActivityCreated
3. 向右滑动1页 从缓存加载页面2,创建页面3,保存页面0的State并销毁(当前缓存1、2、3共3个页面)
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并销毁(当前缓存0、1、2共3个页面)
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(当前可见页面)
四、区别总结
- FragmentPagerAdapter 中所有创建过的Fragment都缓存在FragmentManager中;页面切换,只是调用detach,而不是remove,所以只执行onDestroyView,而不是onDestroy。
- FragmentStatePagerAdapter缓存的个数是(mOffscreenPageLimit * 2 + 1),而不是全部;页面切换,调用remove删除,且要缓存State。
五、Fragment的setUserVisibleHint,实现懒加载
通过上面的分析,我们知道,ViewPager默认会加载mOffscreenPageLimit个页面,并执行onAttach->onCreate->onCreateView->onActivityCreate->onResume。
所以如果在上面的生命周期中处理页面逻辑(比如网络请求),那么可能在页面不可见时就进行了处理。
因此,需要判断Fragment的可见,在可见时加载。就用到setUserVisibleHint。
不过该方法会先于onAttach之前,所以具体实现时需要判断下其他生命周期情况。
注意:setUserVisibleHint是在PagerAdapter类中初始化和滚动时调用的,所以需要配合ViewPager才能使用。