FragmentStatePagerAdapter與FragmentPagerAdapter(開發(fā)所遇到的坑)

最近碰到個比較奇葩的需求,總結(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就可完美解決钳枕。缴渊。。鱼炒。衔沼。。昔瞧。指蚁。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末自晰,一起剝皮案震驚了整個濱河市凝化,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酬荞,老刑警劉巖搓劫,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劣光,死亡現(xiàn)場離奇詭異,居然都是意外死亡糟把,警方通過查閱死者的電腦和手機(jī)绢涡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遣疯,“玉大人雄可,你說我怎么就攤上這事〔” “怎么了数苫?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辨液。 經(jīng)常有香客問我虐急,道長,這世上最難降的妖魔是什么滔迈? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任止吁,我火速辦了婚禮,結(jié)果婚禮上燎悍,老公的妹妹穿的比我還像新娘敬惦。我一直安慰自己,他們只是感情好谈山,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布俄删。 她就那樣靜靜地躺著,像睡著了一般奏路。 火紅的嫁衣襯著肌膚如雪畴椰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天鸽粉,我揣著相機(jī)與錄音斜脂,去河邊找鬼。 笑死潜叛,一個胖子當(dāng)著我的面吹牛秽褒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播威兜,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼销斟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椒舵?” 一聲冷哼從身側(cè)響起蚂踊,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笔宿,沒想到半個月后犁钟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棱诱,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年涝动,在試婚紗的時候發(fā)現(xiàn)自己被綠了迈勋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡醋粟,死狀恐怖靡菇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情米愿,我是刑警寧澤厦凤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站育苟,受9級特大地震影響较鼓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜违柏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一博烂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勇垛,春花似錦脖母、人聲如沸士鸥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烤礁。三九已至讼积,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脚仔,已是汗流浹背勤众。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲤脏,地道東北人们颜。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像猎醇,于是被迫代替她去往敵國和親窥突。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • ViewPager在開發(fā)中的使用頻率非常的高硫嘶,所以在此做個總結(jié)阻问。主要包括以下幾方面: ViewPager的簡介和作...
    西瓜太郎123閱讀 120,742評論 21 261
  • FragmentPagerAdapter的三個調(diào)用方法 為了解決不同ViewPage 和Fragment之間切換時...
    簡康閱讀 1,426評論 0 3
  • 在使用ViewPager常用設(shè)置1)mViewPager.setOffscreenPageLimit(2);//設(shè)...
    kangqiao182閱讀 7,659評論 2 12
  • 撥草。 非得等到五點(diǎn)半再營業(yè)沦疾,有原則的店吶称近。 一鍋夠三個人吃第队!好吃!但沒想象中好吃 新品刨秆,還不錯凳谦,可以免費(fèi)加面 好...
    菠00閱讀 402評論 0 0
  • 獨(dú)自一人 走在無人的街道 品味著寒風(fēng)吹來的孤寂 一旁昏黃的光 給世界遮上了紗帳 使眼前的道路更加模糊 卻不知 看不...
    冬日的雨閱讀 1,320評論 0 3