Android開發(fā)中經(jīng)常用到ViewPager+Fragment+Adapter的場(chǎng)景,一般每個(gè)Fragment控制自己的刷新局蚀,但是如果想要刷新整個(gè)ViewPager怎么做呢麦锯?或者想要將緩存的Fragent給重建怎么做呢?之前做業(yè)務(wù)的時(shí)候遇到一個(gè)問題琅绅,ViewPage在第二次setAdapter的如果用的是FragmentPager并不會(huì)導(dǎo)致頁面刷新扶欣,但是采用FragementStatePagerAdapter卻會(huì)刷新?不由得有些好奇千扶,隨跟蹤了部分源碼料祠,簡(jiǎn)單整理如下:
ViewPager+FragmentPagerAdapter為何不能通過setAdapter做到整體刷新
第二次設(shè)置PagerAdapter的時(shí)候,首先會(huì)將原來的Fragment進(jìn)行清理澎羞,之后在調(diào)用populate()重建髓绽,只是重建的時(shí)候并不一定真的重新創(chuàng)建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) {
<!--重新設(shè)置Fragment-->
populate();
}
...
}
之前說過妆绞,第二次通過setAdapter的方式來設(shè)置ViewPager的FragmentAdapter時(shí)不會(huì)立即刷新的效果顺呕,但是如果往后滑動(dòng)幾屏?xí)l(fā)現(xiàn)其實(shí)是有效果了?為什么呢括饶,因?yàn)榈诙蝧etAdapter的時(shí)候塘匣,已經(jīng)被FragmentManager緩存的Fragent不會(huì)被新建,也不會(huì)被刷新巷帝,因?yàn)镕ragmentPagerAdapter在調(diào)用destroy的時(shí)候,采用的是detach的方式扫夜,并未真正的銷毀Fragment楞泼,僅僅是打算銷毀了View,這就導(dǎo)致FragmentManager中仍舊保留正Fragment的緩存:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 僅僅detach
mCurTransaction.detach((Fragment)object);
}
Transaction.detach函數(shù)最終會(huì)調(diào)用FragmentManager的detachFragment函數(shù)笤闯,將Fragment從當(dāng)前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;
<!--將狀態(tài)設(shè)置為Fragment.CREATED-->
moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
}
}
}
可以看到堕阔,這里僅僅會(huì)將Fragment設(shè)置為Fragment.CREATED,對(duì)于Fragment.CREATED狀態(tài)的Fragment颗味,F(xiàn)ragmentManager是不會(huì)調(diào)用makeInactive進(jìn)行清理的超陆,
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;
}
...
因?yàn)橹挥衜akeInactive才會(huì)清理Fragment的引用如下:
void makeInactive(Fragment f) {
if (f.mIndex < 0) {
return;
}
<!--置空mActive列表對(duì)于Fragment的強(qiáng)引用-->
mActive.set(f.mIndex, null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
mAvailIndices.add(f.mIndex);
mActivity.invalidateFragment(f.mWho);
f.initState();
}
可見,F(xiàn)ragment的緩存仍舊留在FragmentManager中。新的FragmentPagerAdapter被設(shè)置后时呀,會(huì)通過instantiateItem函數(shù)來獲取Fragment张漂,這個(gè)時(shí)候它首先會(huì)從FragmentManager的緩存中去取Fragment,取到的Fragment其實(shí)就是之前未銷毀的Fragment谨娜,就會(huì)導(dǎo)致之前傳遞的參數(shù)仍然是舊的航攒,這也是為什么可能不會(huì)刷新的原因:
@Override
public Object instantiateItem(ViewGroup container, int position) {
<!--新建一個(gè)事務(wù)-->
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
<!--利用id與container的id創(chuàng)建name-->
String name = makeFragmentName(container.getId(), itemId);
<!--根據(jù)name在Activity的FragmentManager中查找緩存Fragment-->
Fragment fragment = mFragmentManager.findFragmentByTag(name);
<!--如果找到的話,直接使用當(dāng)前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對(duì)象的時(shí)候坞靶,首先是通過mFragmentManager.findFragmentByTag(name);查找是否已經(jīng)有Fragment緩存憔狞,第二次設(shè)置Adapter的時(shí)候,由于部分Fragment已經(jīng)被添加到FragmentManager的緩存中去了彰阴,新的Adapter仍然能通過mFragmentManager.findFragmentByTag(name)找到緩存Fragment瘾敢,阻止了Fragment的新建,因此不會(huì)有整體刷新的效果硝枉。那如果想要整體刷新怎么辦呢廉丽?可以使用FragementStatePagerAdapter,兩者對(duì)于Fragment的緩存管理不同妻味。
ViewPager+FragementStatePagerAdapter可以通過setAdapter做到整體刷新
同樣先看一下FragementStatePagerAdapter的destroyItem函數(shù)正压,F(xiàn)ragementStatePagerAdapter在destroyItem的時(shí)候使用的是remove的方式,這種方式對(duì)于沒有添加到回退棧的Fragment操作來說责球,不僅會(huì)銷毀view焦履,還會(huì)銷毀Fragment。
@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會(huì)首先通過mFragments.set(position, null)清理自己的緩存雏逾,然后嘉裤,通過Transaction.remove清理在FragmentManager中的緩存,Transaction.remove最終會(huì)調(diào)用FragmentManager的removeFragment函數(shù):
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
<!-- 其實(shí)兩者的主要區(qū)別就是看是否在回退棧栖博,如果在屑宠,表現(xiàn)就一致,如果不在仇让,表現(xiàn)不一致-->
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
...
fragment.mAdded = false;
fragment.mRemoving = true;
<!--將狀態(tài)設(shè)置為Fragment.CREATED或者Fragment.INITIALIZING-->
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle, false);
}
}
FragementStatePagerAdapter中的Fragment在添加的時(shí)候典奉,都沒有addToBackStack,所以moveToState會(huì)將狀態(tài)設(shè)置為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卫玖,這里一般會(huì)調(diào)用makeInactive函數(shù)清理Fragment的引用,這里其實(shí)就算銷毀了Fragment在FragmentManager中的緩存踊淳。
ViewPager通過populate因此再次新建的時(shí)候假瞬,F(xiàn)ragementStatePagerAdapter的instantiateItem 一定會(huì)新建Fragment,因?yàn)橹暗腇ragment已經(jīng)被清理掉了,在自己的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;
}
}
...
<!--關(guān)鍵點(diǎn) 如果在FragementStatePagerAdapter找不到粗俱,直接新建,不關(guān)心FragmentManager中是否有-->
Fragment fragment = getItem(position);
<!--查看是否需恢復(fù)虚吟,如果需要寸认,則恢復(fù)-->
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;
}
從上面代碼也可以看出,F(xiàn)ragementStatePagerAdapter在新建Fragment的時(shí)候串慰,不會(huì)去FragmentMangerImpl中去取偏塞,而是直接在FragementStatePagerAdapter的緩存中取,如果取不到邦鲫,則直接新建Fragment灸叼,如果通過setAdapter設(shè)置了新的FragementStatePagerAdapter,一定會(huì)新建所有的Fragment庆捺,就能夠達(dá)到整體刷新的效果古今。
FragmentPagerAdapter如何通過notifyDataSetChanged刷新ViewPager
FragmentPagerAdapter中的數(shù)據(jù)發(fā)生改變時(shí),往往要重新將數(shù)據(jù)設(shè)置到Fragment滔以,或者干脆新建Fragment捉腥,而對(duì)于用FragmentPagerAdapter的ViewPager來說,只是利用其notifyDataSetChanged是不夠的你画,跟蹤源碼會(huì)發(fā)現(xiàn)抵碟,notifyDataSetChanged最終會(huì)調(diào)用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();
}
}
默認(rèn)情況下FragmentPagerAdapter中的getItemPosition返回的是PagerAdapter.POSITION_UNCHANGED,所以這里不會(huì)
destroyItem坏匪,即時(shí)設(shè)置了PagerAdapter.POSITION_NONE拟逮,調(diào)用了其destroyItem,也僅僅是detach适滓,銷毀了View敦迄,F(xiàn)ragment仍舊不會(huì)重建,必須手動(dòng)更改參數(shù)才可以凭迹,這個(gè)時(shí)機(jī)在哪里呢罚屋?FragmentAdapter的getItem函數(shù)會(huì)在第一次需要?jiǎng)?chuàng)建Fragment的時(shí)候調(diào)用,如果需要將參數(shù)傳遞給Fragment蕊苗,可以通過Fragment.setArguments()來設(shè)置,但是僅僅在getItem新建的時(shí)候有效沿彭,一旦被Fragment被創(chuàng)建朽砰,就會(huì)被FragmentManager緩存,如果不主動(dòng)釋放,對(duì)于當(dāng)前位置的Fragment來說瞧柔,getItem函數(shù)是不會(huì)再次被調(diào)用的漆弄,原因已經(jīng)在上文的instantiateItem函數(shù)處說明了,它會(huì)首先去緩存中取造锅。那這個(gè)時(shí)候撼唾,如何更新呢?Fragment.setArguments是不能再調(diào)用的哥蔚,因?yàn)楸籥ttach過的Fragment來說不能再次通過setArguments被設(shè)置參數(shù)倒谷,否則拋出異常
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
那如果真要更改就需要在其instantiateItem的時(shí)候强经,通過額外的接口手動(dòng)設(shè)置俯艰,同時(shí)也必須將getItemPosition返回值設(shè)置為POSITION_NONE,這樣才會(huì)每次都走View的新建流程碴卧,才有可能刷新:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
至于參數(shù)如何設(shè)置呢深夯?這里就需要用戶手動(dòng)提供接口變更參數(shù)了抖格,在自定義的FragmentAdapter覆蓋instantiateItem,自己手動(dòng)獲取緩存Fragment咕晋,在attach之前雹拄,將參數(shù)給重新設(shè)置進(jìn)去,之后掌呜,F(xiàn)ragment在走onCreateView流程的時(shí)候滓玖,就會(huì)獲取到新的參數(shù)。
@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的刷新呢撞。并且到這里我們也知道了,對(duì)于FragmentPagerAdapter來說饰剥,用戶完全不需要自己緩存Fragment殊霞,只需要緩存View,因?yàn)镕ragmentPagerAdapter不會(huì)銷毀Fragment汰蓉,也不會(huì)銷毀FragmentManager中緩存的Fragment绷蹲,至于緩存的View要不要刷新,可能就要你具體的業(yè)務(wù)需求了顾孽。
FragmentStatePagerAdapter如何通過notifyDataSetChanged刷新ViewPager頁面
對(duì)于FragmentStatePagerAdapter相對(duì)容易些祝钢,如果不需要考慮效率,重建所有的Fragment即可若厚,只需要復(fù)寫其getItemPosition函數(shù)
public int getItemPosition(Object object) {
return POSITION_NONE;
}
因?yàn)镕ragmentStatePagerAdapter中會(huì)真正的remove Fragment拦英,達(dá)到完全重建的效果。
Fragmentmanager Transaction棧的意義
最后看一下Fragmentmanager中Transaction棧测秸,F(xiàn)ragmentManager的Transaction棧到底是做什么的呢疤估?FragmentManager對(duì)于Fragment的操作是分批量進(jìn)行的灾常,在一個(gè)Transaction中有多個(gè)add、remove铃拇、attach操作钞瀑,Android是有返回鍵的,為了支持點(diǎn)擊返回鍵恢復(fù)上一個(gè)場(chǎng)景的操作慷荔,Android的Fragment管理引入Transaction棧雕什,更方便回退,其實(shí)將一個(gè)Transaction的操作全部翻轉(zhuǎn):添加變刪除显晶、attach變detach贷岸,反之亦然。對(duì)于每個(gè)入棧的Transaction吧碾,都是需要出棧的凰盔,而且每個(gè)操作都有前后文,比如進(jìn)入與退出的動(dòng)畫倦春,當(dāng)需要翻轉(zhuǎn)這個(gè)操作户敬,也就是點(diǎn)擊返回鍵的時(shí)候,需要知道如何翻轉(zhuǎn)睁本,也就是需要記錄當(dāng)前場(chǎng)景尿庐,對(duì)于remove,如果沒有入棧操作呢堰,說明不用記錄上下文抄瑟,可以直接清理掉。對(duì)于ViewPager在使用FragmentPagerAdapter/FragmentStatePagerAdapter的時(shí)候都不會(huì)addToBackStack枉疼,這也是為什么detach跟remove有時(shí)候表現(xiàn)一致或者不一致的原因皮假。簡(jiǎn)單看一下出棧操作,其實(shí)就是將原來從操作翻轉(zhuǎn)一遍骂维,當(dāng)然惹资,并不是完全照搬,還跟當(dāng)前的Fragment狀體有關(guān)航闺。
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對(duì)于Fragment的緩存管理
FragmentManager主要維護(hù)三個(gè)重要List褪测,一個(gè)是mActive Fragment列表,一個(gè)是mAdded FragmentList潦刃,還有個(gè)BackStackRecord回退棧
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
ArrayList<BackStackRecord> mBackStack;
mAdded列表是被當(dāng)前添加到Container中去的侮措,而mActive是全部參與的Fragment集合,只要沒有被remove乖杠,就會(huì)一致存在分扎,可以認(rèn)為mAdded的Fragment都是活著的,而mActive的Fragment卻可能被處決胧洒,并被置null畏吓,只有makeInactive函數(shù)會(huì)這么做环揽。
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就是從這兩個(gè)列表中讀取的 。
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;
}
總結(jié)
本文簡(jiǎn)單分析了下ViewPager在使用FrgmentPagerAdapter跟FragmentStatePagerAdapter遇到問題庵佣,原理、及問題的解決方案汛兜。
作者:看書的小蝸牛
原文鏈接:ViewPager刷新問題原理分析及解決方案(FragmentPagerAdapter+FragementStatePagerAdapter)
僅供參考巴粪,歡迎指正