##1、概述 PagerAdapter
是提供计算ViewPager
内的Pages的适配器,而FragmentPagerAdapter
与FragmentStatePagerAdapter
都是继承至 PagerAdapter
这个基类,是PagerAdapter
的两个特殊实现。可能有些人会断章取义的认为FragmentPagerAdapter
不会保存Fragment
的状态,而FragmentStatePagerAdapter
会维持Fragment
的状态。事实上,这是一种错误的理解,虽然你在使用时并不会有什么影响。FragmentPagerAdapter
与FragmentStatePagerAdapter
的实现跟它们所要服务的场景有密切的关系,我们通过理解二者的源码来了解设计者的设计目的。
##2、FragmentPagerAdapter
首先先贴上源代码,代码量不是很多
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
@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;
}
@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);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
/**
* Return a unique identifier for the item at the given position.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
}
我们把目光聚焦到这几个主要的方法:
public Object instantiateItem(ViewGroup container, int position)
public void destroyItem(ViewGroup container, int position, Object object)
public void finishUpdate(ViewGroup container)
public Parcelable saveState()
public void restoreState(Parcelable state, ClassLoader loader)
方法saveState
和restoreState
并没有干什么事情,但是在FragmentStatePagerAdapter
中就大不一样,所以现在先注意这两个方法。 finishUpdate
当已显示的pages完成改变后调用,一般在这个方法内commit FragmentTransaction
的操作。我们首先看看instantiateItem
方法的源代码,它是用来实例化某个item的:
@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;
}
这个方法首先获得一个itemId
(实际上返回时的position本身),然后作为参数传入makeFragmentName方法生成一个name,然后使用这个name 在FragmentManager
内搜索出Fragment,如果不为空,说明以前加入过,那么重新attach这个Fragment,如果为空,那么使用getItem
创建一个,getItem
就是需要我们自己实现的抽象方法之一,最后将新创建的Fragment
add进去。
既然FragmentPagerAdapter
实例化item得方式是通过add
或者attach
,那么,显而易见,在destroyItem
内必定是使用detach
来‘卸载’item:
@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);
}
果然,很简单的操作,使用FragmentTransaction
attach了需要销毁的Fragment。
FragmentPagerAdapter
整个流程很简单,就是 add -> detach -> attach -> detach -> …因为走的是detach
和attach
的路,所以系统会保存Fragment
的State。FragmentPagerAdapter
是很普通的一个PagerAdapter
的实现类,适用于基本的使用场景,但是如果是有大量的Tab的使用场景,FragmentPagerAdapter
就不太适用了,因为它的状态都用系统保存常驻在内存之中了,并且Fragment
的实例也常驻在内存,所以会导致大量的内存占用。
FragmentStatePagerAdapter
就解决FragmentPagerAdapter
短板的问题。
##3、FragmentStatePagerAdapter
FragmentStatePagerAdapter
相对FragmentPagerAdapter
多了两个变量:
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
意图很明显了,mFragments
用来保存Fragment
的实例,mSavedState
用来保存每个Fragment
的状态,我们接下来看看instantiateItem
和destroyItem
这两个方法:
@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;
}
FragmentStatePagerAdapter
首先会在mFragments
集合内寻找对应position的Fragment
,如果position大于该集合的size,说明该position下的Fragment
从未被访问过,那么就会执行getItem
创建新的实例。如果position在mSavedState
集合范围内,说明改Fragment
曾经访问过,并且有它以前的状态,那么,还原这个状态。最后加入FragmentManager
之中。再来看看destroyItem
方法:
@Override
public void destroyItem(ViewGroup container, int position, 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);
}
不同于FragmentPagerAdapter
,FragmentStatePagerAdapter
是采用remove
的方式销毁Fragment
,但实际上,活动的Fragment
实例保存在mFragments
之中,在destroyItem
方法内又会被移除,但是状态不会被删除,总是保存在mSavedState
集合之中。所以,FragmentStatePagerAdapter
的机制是: add -> save state -> remove -> initial state -> add -> …
这里有也暴露了FragmentStatePagerAdapter
两个致命的问题:一是状态不会被删除,总是保存在mSavedState
集合中,如果一个ViewPager
像网易新闻那样有几十个Tab,势必会造成内存压力。二是不能做移除Tab的工作,即使你移除的某个Tab以及相关的Fragment
,状态依然没有删除,我们又不能删除,没有相关的API,同时,例如你删除了position为2的Fragment
,原本position为3的Fragment
就会使用position为2的Fragment
的状态。这是很致命的问题。
##4、区别在哪?
FragmentPagerAdapter
和FragmentStatePagerAdapter
的机制决定了它们的区别。FragmentPagerAdapter
使用add
, attach
, detach
来管理Fragment
,Fragment
实例和状态都被保存下来,但是重建的消耗不高,生命周期在onAttach和onDetach间游走,典型的用内存换效率的做法。而FragmentStatePagerAdapter
使用add
, remove
来管理Fragment
,被销毁的Fragment
实例不再存在,但是其状态保存在集合之中,以便下次重新创建实例时能够还原之前的状态。