最近碰到個比較奇葩的需求,總結(jié)問題就是ViewPager和Fragment搭配使用(3種以上類型的Fragment)敛惊,F(xiàn)ragment列表中的不同種Fragment順序改變渊鞋,或者刪除增加導(dǎo)致的各種崩潰問題绰更,不論使用FragmentPagerAdapter還是FragmentStatePagerAdapter都會出現(xiàn)不同類型的錯誤瞧挤,也讓我看到了這兩種PageAdapter的局限性,在此總結(jié)儡湾,希望能幫到遇到同樣問題的親們特恬。
本篇內(nèi)容
一、FragmentPagerAdapter和FragmentStatePagerAdapter的區(qū)別以及局限性
二徐钠、由于ViewPager中Fragment列表的增刪和順序變化癌刽,導(dǎo)致ViewPager調(diào)用notifyDataSetChanged()方法出現(xiàn)異常或應(yīng)用崩潰的解決方案
————————————————————————————————
內(nèi)容一:FragmentPagerAdapter和FragmentStatePagerAdapter的區(qū)別以及局限性
FragmentPagerAdapter和FragmentStatePagerAdapter尝丐,這兩個adapter剛開始用的時候分不清显拜,不過似乎不論用哪個adapter出來的效果都差不多,用的多的人知道爹袁,當(dāng)viewpager中fragment數(shù)量多的時候用FragmentStatePagerAdapter远荠,反之則用FragmentPagerAdapter。在這里我想從兩個問題展開失息,看清這兩個問題譬淳,大家就一目了然了:
1.兩種adapter存儲/恢復(fù)fragment的區(qū)別
2.兩種adapter銷毀fragment的區(qū)別
存儲和恢復(fù):
FragmentPagerAdapter
初始化方法:
@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 fragment = mFragmentManager.findFragmentByTag(name);//從manager中找出該名字的fragment
? ? if (fragment != null) {
? ? ? ? if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
? ? ? ? mCurTransaction.attach(fragment);
? ? } else {
? ? ? ? /*
? ? ? 繼承FragmentPagerAdapter要實(shí)現(xiàn)的getItem方法,從源碼中可以看到盹兢,
? ? ? 不是viewpager跳轉(zhuǎn)到哪個fragment就會調(diào)用getItem方法邻梆,
? ? ? 而是只要manager中沒有這個fragment才會調(diào)用,
? ? ? 一個fragment只會調(diào)用一次這個方法
? ? ? */
? ? ? ? fragment = getItem(position);
? ? ? ? if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
? ? ? ? mCurTransaction.add(container.getId(), fragment,
? ? ? ? ? ? ? ? makeFragmentName(container.getId(), itemId));//將命名的fragment放入manager進(jìn)行管理
? ? }
? ? if (fragment != mCurrentPrimaryItem) {
? ? ? ? fragment.setMenuVisibility(false);
? ? ? ? fragment.setUserVisibleHint(false);
? ? }
? ? return fragment;
}
以上代碼要注意兩點(diǎn)绎秒,一個是getItem方法的調(diào)用時機(jī)浦妄,只有當(dāng)manager中沒有該Fragment的時候才會被調(diào)用,一個Fragment只會觸發(fā)這一次见芹,原因就是要注意的第二點(diǎn)校辩,方法makeFragmentName(),通過父容器container的Id和getItemId()方法返回的值(不重寫該方法就返回fragment的position值)辆童,為每一個fragment命名宜咒,然后通過鍵值對的方式,放入到manager中把鉴,從而達(dá)到存儲fragment和恢復(fù)fragment的目的故黑。然而這個makeFragmentName()也就是當(dāng)fragment列表順序改變(即position值改變)導(dǎo)致崩潰的原因儿咱。
因此這個FragmentPagerAdapter不適用于fragment列表順序改變,中間添加fragment或者刪除某個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);//僅detach該fragment 沒做任何釋放內(nèi)存操作
}
這是銷毀fragment的方法混埠,可以看到FragmentPagerAdapter不是將不可見的fragment銷毀,而是僅僅將該fragment從頁面中detach掉诗轻,fragment還是在manager中保存钳宪,內(nèi)存沒有被釋放,從這邊可以看到FragmentPagerAdapter不適合fragment數(shù)量多的情況下使用扳炬,因?yàn)槲幢会尫诺膄ragment會占用大量內(nèi)存吏颖。
FragmentStatePagerAdapter
初始化方法
/*
? FragmentStatePagerAdapter是通過mFragments數(shù)組來存儲fragment的,通過mSavedState列表
? 來存儲fragment銷毀時的狀態(tài)恨樟,通過position獲取到的fragment可能為空(被回收)半醉,如果為空,則會
? 再次調(diào)用getItem方法重新創(chuàng)建新的fragment劝术,然后將mSavedState中存儲的狀態(tài)重新賦予這個新的fragment缩多,
? 達(dá)到fragment恢復(fù)的效果
*/
@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);//創(chuàng)建fragment 因?yàn)閒ragment頻繁的創(chuàng)建導(dǎo)致這個getItem方法會被多次調(diào)用 區(qū)別于FragmentPagerAdapter
? ? if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
? ? if (mSavedState.size() > position) {
? ? ? ? Fragment.SavedState fss = mSavedState.get(position);//從存儲fragment的列表中取出對應(yīng)位置fragment保存的狀態(tài)
? ? ? ? if (fss != null) {
? ? ? ? ? ? fragment.setInitialSavedState(fss);//將之前fragment狀態(tài)賦予新的fragment
? ? ? ? }
? ? }
? ? while (mFragments.size() <= position) {
? ? ? ? mFragments.add(null);
? ? }
? ? fragment.setMenuVisibility(false);
? ? fragment.setUserVisibleHint(false);
? ? mFragments.set(position, fragment);
? ? mCurTransaction.add(container.getId(), fragment);
? ? return fragment;
}
以上代碼的中mFragments和mSavedState是按順序一一對應(yīng)的,每當(dāng)一個fragment重建都會從mSavedState中找到相應(yīng)的狀態(tài)养晋,如果mFragment中的某一個fragment順序改變衬吆,那么mFragments和mSavedState就不再一一對應(yīng),導(dǎo)致fragment恢復(fù)時出現(xiàn)異常情況绳泉。
同樣這個FragmentStatePagerAdapter不適用于fragment列表順序改變逊抡,中間添加fragment或者刪除某個fragment的情形。
銷毀方法:
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);//釋放引用之前保存該位置fragment的狀態(tài)
? ? mFragments.set(position, null);//將mFragment列表中該位置的fragment至空圈纺,釋放引用
? ? mCurTransaction.remove(fragment);//同時將manager中的該fragment釋放
}
FragmentPagerAdapter銷毀fragment方法秦忿,可以看到,當(dāng)fragment在頁面中不可見時蛾娶,該fragment的狀態(tài)會先被保存到mSavedState中灯谣,而fragment實(shí)例則會被銷毀,在對應(yīng)的instantiateItem方法中蛔琅,fragment會被重新創(chuàng)建胎许,并將mSavedState中對應(yīng)狀態(tài)賦予該剛剛創(chuàng)建的新fragment,從而達(dá)到恢復(fù)之前fragment和節(jié)省內(nèi)存的效果罗售,因此FragmentStatePagerAdapter適合有較多fragment情況
————————————————————————————————
內(nèi)容二辜窑,由于ViewPager中Fragment列表的增刪和順序變化,導(dǎo)致ViewPager調(diào)用notifyDataSetChanged()方法出現(xiàn)異痴辏或應(yīng)用崩潰的解決方案.
看完以上源碼穆碎,相信大家應(yīng)該明白,發(fā)生fragment列表順序改變职恳,中間添加fragment或者刪除某個fragment這些情況的時候所禀,為什么會出現(xiàn)異常等情況方面。我們需要根據(jù)需求,定制這兩種不同的adapter色徘,所以在此給出發(fā)生這些異常時的解決思路:
1恭金、若Fragment數(shù)量較少,可采用FragmentPagerAdapter的方式褂策,要做的是定制makeFragmentName()該方法横腿,讓instantiateItem獲取fragment時不根據(jù)position和itemid值,而是根據(jù)你的需求對fragment進(jìn)行命名斤寂,同時當(dāng)fragment列表數(shù)量減少時耿焊,從manager中刪除被刪除的fragment引用,讓其內(nèi)存得到釋放扬蕊。
2搀别、若Fragment數(shù)量較多丹擎,可采用FragmentStatePagerAdapter的方式尾抑,要做的是改變其中mFragments和mSavedState的對應(yīng)關(guān)系,不再根據(jù)position進(jìn)行一一對應(yīng)蒂培。
(我說的方式當(dāng)然是把兩個adapter的源碼復(fù)制一遍然后修改啦T儆)
最后請注意重寫adapter中的這個方法
@Override
public int getItemPosition(Object object) {
? ? //object參數(shù)是當(dāng)前位置的fragment
? ? //return POSITION_UNCHANGED;
? ? //return POSITION_NONE;
}
果返回POSITION_UNCHANGED,那么該位置的fragment是不會被更新的护戳,POSITION_NONE才會重新更新這個位置fragment
例子:如果要更新的fragment列表中第一個fragment是固定不變的翎冲,那么這個位置的fragment可以返回POSITION_UNCHANGED,而其他位置改變的fragment則要返回POSITION_NONE媳荒。
---------------------
當(dāng)
viewpager+tablayout+fragment切換時嵌套復(fù)用的時候抗悍,fragment與對應(yīng)顯示內(nèi)容不符時,使用FragmentStatePagerAdapter就可完美解決钳枕。缴渊。。鱼炒。衔沼。。昔瞧。指蚁。。