Android開發中常常用到ViewPager+Fragment+Adapter的場景,通常每一個Fragment控制本身的刷新,可是若是想要刷新整個ViewPager怎麼作呢?或者想要將緩存的Fragent給重建怎麼作呢?以前作業務的時候遇到一個問題,ViewPage在第二次setAdapter的若是用的是FragmentPager並不會致使頁面刷新,可是採用FragementStatePagerAdapter卻會刷新?不禁得有些好奇,隨跟蹤了部分源碼,簡單整理以下:android
第二次設置PagerAdapter的時候,首先會將原來的Fragment進行清理,以後在調用populate()重建,只是重建的時候並不必定真的從新建立Fragment,以下:緩存
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
...
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
<!--所有destroy-->
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
<!--清理-->
mItems.clear();
removeNonDecorViews();
<!--重置位置-->
mCurItem = 0;
scrollTo(0, 0);
}
...
if (!wasFirstLayout) {
<!--從新設置Fragment-->
populate();
}
...
}
複製代碼
以前說過,第二次經過setAdapter的方式來設置ViewPager的FragmentAdapter時不會當即刷新的效果,可是若是日後滑動幾屏會發現實際上是有效果了?爲何呢,由於第二次setAdapter的時候,已經被FragmentManager緩存的Fragent不會被新建,也不會被刷新,由於FragmentAdapter在調用destroy的時候,採用的是detach的方式,並未真正的銷燬Fragment,僅僅是打算銷燬了View,這就致使FragmentManager中仍舊保留正Fragment的緩存:ide
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 僅僅detach
mCurTransaction.detach((Fragment)object);
}
複製代碼
Transaction.detach函數最終會調用FragmentManager的detachFragment函數,將Fragment從當前Activity detach函數
public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
if (!fragment.mDetached) {
<!--只是detach -->
fragment.mDetached = true;
if (fragment.mAdded) {
<!--若是是被added 從added列表中移除-->
if (mAdded != null) {
mAdded.remove(fragment);
}
...
fragment.mAdded = false;
<!--將狀態設置爲Fragment.CREATED-->
moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
}
}
}
複製代碼
能夠看到,這裏僅僅會將Fragment設置爲Fragment.CREATED,對於Fragment.CREATED狀態的Fragment,FragmentManager是不會調用makeInactive進行清理的,動畫
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
...
if (!keepActive) {
if (!f.mRetaining) {
makeInactive(f);
} else {
f.mActivity = null;
f.mParentFragment = null;
f.mFragmentManager = null;
}
...
複製代碼
由於只有makeInactive纔會清理Fragment的引用以下:this
void makeInactive(Fragment f) {
if (f.mIndex < 0) {
return;
}
<!--置空mActive列表對於Fragment的強引用-->
mActive.set(f.mIndex, null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
mAvailIndices.add(f.mIndex);
mActivity.invalidateFragment(f.mWho);
f.initState();
}
複製代碼
可見,Fragment的緩存仍舊留在FragmentManager中。新的FragmentPagerAdapter被設置後,會經過instantiateItem函數來獲取Fragment,這個時候它首先會從FragmentManager的緩存中去取Fragment,取到的Fragment其實就是以前未銷燬的Fragment,這也是爲何不會刷新的緣由:spa
@Override
public Object instantiateItem(ViewGroup container, int position) {
<!--新建一個事務-->
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
<!--利用id與container的id建立name-->
String name = makeFragmentName(container.getId(), itemId);
<!--根據name在Activity的FragmentManager中查找緩存Fragment-->
Fragment fragment = mFragmentManager.findFragmentByTag(name);
<!--若是找到的話,直接使用當前Fragment-->
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
<!--若是找不到則新建,並新建name,添加到container中去-->
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
複製代碼
從上面代碼能夠看到,在新建Fragment對象的時候,首先是經過mFragmentManager.findFragmentByTag(name);查找是否已經有Fragment緩存,第二次設置Adapter的時候,因爲部分Fragment已經被添加到FragmentManager的緩存中去了,新的Adapter仍然能經過mFragmentManager.findFragmentByTag(name)找到緩存Fragment,阻止了Fragment的新建,所以不會有總體刷新的效果。那若是想要總體刷新怎麼辦呢?可使用FragementStatePagerAdapter,二者對於Fragment的緩存管理不一樣。code
一樣先看一下FragementStatePagerAdapter的destroyItem函數,FragementStatePagerAdapter在destroyItem的時候使用的是remove的方式,這種方式對於沒有添加到回退棧的Fragment操做來講,不只會銷燬view,還會銷燬Fragment。cdn
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
<!--FragementStatePagerAdapter先清理本身的緩存-->
mFragments.set(position, null);
<!--直接刪除-->
mCurTransaction.remove(fragment);
}
複製代碼
可見FragementStatePagerAdapter會首先經過mFragments.set(position, null)清理本身的緩存,而後,經過Transaction.remove清理在FragmentManager中的緩存,Transaction.remove最終會調用FragmentManager的removeFragment函數:對象
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
<!-- 其實二者的主要區別就是看是否在回退棧,若是在,表現就一致,若是不在,表現不一致-->
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
...
fragment.mAdded = false;
fragment.mRemoving = true;
<!--將狀態設置爲Fragment.CREATED或者Fragment.INITIALIZING-->
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle, false);
}
}
複製代碼
FragementStatePagerAdapter中的Fragment在添加的時候,都沒有addToBackStack,因此moveToState會將狀態設置爲Fragment.INITIALIZING ,
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
...
if (!keepActive) {
if (!f.mRetaining) {
makeInactive(f);
} else {
f.mActivity = null;
f.mParentFragment = null;
f.mFragmentManager = null;
}
...
複製代碼
Fragment.INITIALIZING < Fragment.CREATED,這裏通常會調用makeInactive函數清理Fragment的引用,這裏其實就算銷燬了Fragment在FragmentManager中的緩存。
ViewPager經過populate所以再次新建的時候,FragementStatePagerAdapter的instantiateItem 必定會新建Fragment,由於以前的Fragment已經被清理掉了,在本身的Fragment緩存列表中取不到,就新建。看以下代碼:
@Override
public Object instantiateItem(ViewGroup container, int position) {
<!--查看FragementStatePagerAdapter中是否有緩存的Fragment,若是有直接返回-->
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
<!--關鍵點 若是在FragementStatePagerAdapter找不到,直接新建,不關心FragmentManager中是否有-->
Fragment fragment = getItem(position);
<!--查看是否需恢復,若是須要,則恢復-->
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
...
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
複製代碼
從上面代碼也能夠看出,FragementStatePagerAdapter在新建Fragment的時候,不會去FragmentMangerImpl中去取,而是直接在FragementStatePagerAdapter的緩存中取,若是取不到,則直接新建Fragment,若是經過setAdapter設置了新的FragementStatePagerAdapter,必定會新建全部的Fragment,就可以達到總體刷新的效果。
FragmentPagerAdapter中的數據發生改變時,每每要從新將數據設置到Fragment,或者乾脆新建Fragment,而對於用FragmentPagerAdapter的ViewPager來講,只是利用其notifyDataSetChanged是不夠的,跟蹤源碼會發現,notifyDataSetChanged最終會調用ViewPager中的dataSetChanged:
void dataSetChanged() {
...
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
...
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
...
continue;
}
...
if (needPopulate) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
複製代碼
默認狀況下FragmentPagerAdapter中的getItemPosition返回的是PagerAdapter.POSITION_UNCHANGED,因此這裏不會 destroyItem,即時設置了PagerAdapter.POSITION_NONE,調用了其destroyItem,也僅僅是detach,銷燬了View,Fragment仍舊不會重建,必須手動更改參數才能夠,這個時機在哪裏呢?FragmentAdapter的getItem函數會在第一次須要建立Fragment的時候調用,若是須要將參數傳遞給Fragment,能夠經過Fragment.setArguments()來設置,可是僅僅在getItem新建的時候有效,一旦被Fragment被建立,就會被FragmentManager緩存,若是不主動釋放,對於當前位置的Fragment來講,getItem函數是不會再次被調用的,緣由已經在上文的instantiateItem函數處說明了,它會首先去緩存中取。那這個時候,如何更新呢?Fragment.setArguments是不能再調用的,由於被attach過的Fragment來講不能再次經過setArguments被設置參數,不然拋出異常
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
複製代碼
那若是真要更改就須要在其instantiateItem的時候,經過額外的接口手動設置,同時也必須將getItemPosition返回值設置爲POSITION_NONE,這樣纔會每次都走View的新建流程,纔有可能刷新:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
複製代碼
至於參數如何設置呢?這裏就須要用戶手動提供接口變動參數了,在自定義的FragmentAdapter覆蓋instantiateItem,本身手動獲取緩存Fragment,在attach以前,將參數給從新設置進去,以後,Fragment在走onCreateView流程的時候,就會獲取到新的參數。
@Override
public Object instantiateItem(ViewGroup container, int position) {
String name = makeFragmentName(container.getId(), position);
Fragment fragment =((FragmentActivity) container.getContext()).getSupportFragmentManager().findFragmentByTag(name);
if(fragment instanceof MyFragment){
Bundle bundle=new Bundle();
bundle.putString("msg",""+System.currentTimeMillis());
( (MyFragment) fragment).resetArgument(bundle);
}
return super.instantiateItem(container, position);
}
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
複製代碼
如此,即可以完成FragmentPagerAdapter中Fragment的刷新。而且到這裏咱們也知道了,對於FragmentPagerAdapter來講,用戶徹底不須要本身緩存Fragment,只須要緩存View,由於FragmentPagerAdapter不會銷燬Fragment,也不會銷燬FragmentManager中緩存的Fragment,至於緩存的View要不要刷新,可能就要你具體的業務需求了。
對於FragmentStatePagerAdapter相對容易些,若是不須要考慮效率,重建全部的Fragment便可,只須要複寫其getItemPosition函數
public int getItemPosition(Object object) {
return POSITION_NONE;
}
複製代碼
由於FragmentStatePagerAdapter中會真正的remove Fragment,達到徹底重建的效果。
最後看一下Fragmentmanager中Transaction棧,FragmentManager的Transaction棧究竟是作什麼的呢?FragmentManager對於Fragment的操做是分批量進行的,在一個Transaction中有多個add、remove、attach操做,Android是有返回鍵的,爲了支持點擊返回鍵恢復上一個場景的操做,Android的Fragment管理引入Transaction棧,更方便回退,其實將一個Transaction的操做所有翻轉:添加變刪除、attach變detach,反之亦然。對於每一個入棧的Transaction,都是須要出棧的,並且每一個操做都有先後文,好比進入與退出的動畫,當須要翻轉這個操做,也就是點擊返回鍵的時候,須要知道如何翻轉,也就是須要記錄當前場景,對於remove,若是沒有入棧操做,說明不用記錄上下文,能夠直接清理掉。對於ViewPager在使用FragmentPagerAdapter/FragmentStatePagerAdapter的時候都不會addToBackStack,這也是爲何detach跟remove有時候表現一致或者不一致的緣由。簡單看一下出棧操做,其實就是將原來從操做翻轉一遍,固然,並非徹底照搬,還跟當前的Fragment狀體有關。
public void popFromBackStack(boolean doStateMove) {
Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
} break;
case OP_REPLACE: {
Fragment f = op.fragment;
if (f != null) {
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
if (op.removed != null) {
for (int i=0; i<op.removed.size(); i++) {
Fragment old = op.removed.get(i);
old.mNextAnim = op.popEnterAnim;
mManager.addFragment(old, false);
}
}
} break;
...
複製代碼
FragmentManager主要維護三個重要List,一個是mActive Fragment列表,一個是mAdded FragmentList,還有個BackStackRecord回退棧
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
ArrayList<BackStackRecord> mBackStack;
複製代碼
mAdded列表是被當前添加到Container中去的,而mActive是所有參與的Fragment集合,只要沒有被remove,就會一致存在,能夠認爲mAdded的Fragment都是活着的,而mActive的Fragment卻可能被處決,並被置null,只有makeInactive函數會這麼作。
void makeInactive(Fragment f) {
if (f.mIndex < 0) {
return;
}
mActive.set(f.mIndex, null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
mAvailIndices.add(f.mIndex);
mActivity.invalidateFragment(f.mWho);
f.initState();
}
複製代碼
FragmentPagerAdapter獲取試圖獲取的Fragment就是從這兩個列表中讀取的 。
public Fragment findFragmentByTag(String tag) {
if (mAdded != null && tag != null) {
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
if (mActive != null && tag != null) {
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
return null;
}
複製代碼
本文簡單分析了下ViewPager在使用FrgmentPagerAdapter跟FragmentStatePagerAdapter遇到問題,原理、及問題的解決方案。
做者:看書的小蝸牛 原文連接:ViewPager刷新問題原理分析及解決方案(FragmentPagerAdapter+FragementStatePagerAdapter)
僅供參考,歡迎指正