##一、概述 PagerAdapter
是提供計算ViewPager
內的Pages的適配器,而FragmentPagerAdapter
與FragmentStatePagerAdapter
都是繼承至 PagerAdapter
這個基類,是PagerAdapter
的兩個特殊實現。可能有些人會斷章取義的認爲FragmentPagerAdapter
不會保存Fragment
的狀態,而FragmentStatePagerAdapter
會維持Fragment
的狀態。事實上,這是一種錯誤的理解,雖然你在使用時並不會有什麼影響。FragmentPagerAdapter
與FragmentStatePagerAdapter
的實現跟它們所要服務的場景有密切的關係,咱們經過理解兩者的源碼來了解設計者的設計目的。java
##二、FragmentPagerAdapterandroid
首先先貼上源代碼,代碼量不是不少app
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; } }
咱們把目光聚焦到這幾個主要的方法:ide
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的:ui
@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進去。this
既然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。rest
FragmentPagerAdapter
整個流程很簡單,就是 add -> detach -> attach -> detach -> ...由於走的是detach
和attach
的路,因此係統會保存Fragment
的State。FragmentPagerAdapter
是很普通的一個PagerAdapter
的實現類,適用於基本的使用場景,可是若是是有大量的Tab的使用場景,FragmentPagerAdapter
就不太適用了,由於它的狀態都用系統保存常駐在內存之中了,而且Fragment
的實例也常駐在內存,因此會致使大量的內存佔用。code
FragmentStatePagerAdapter
就解決FragmentPagerAdapter
短板的問題。繼承
##三、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
的狀態。這是很致命的問題。
##四、區別在哪?
FragmentPagerAdapter
和FragmentStatePagerAdapter
的機制決定了它們的區別。FragmentPagerAdapter
使用add
, attach
, detach
來管理Fragment
,Fragment
實例和狀態都被保存下來,可是重建的消耗不高,生命週期在onAttach和onDetach間遊走,典型的用內存換效率的作法。而FragmentStatePagerAdapter
使用add
, remove
來管理Fragment
,被銷燬的Fragment
實例再也不存在,可是其狀態保存在集合之中,以便下次從新建立實例時可以還原以前的狀態。