【轉(zhuǎn)載】Android實戰(zhàn)技巧:Fragment的那些坑

稀有猿訴
十年磨一劍障陶,歷煉出鋒芒滋恬,說話千百句,不如碼二行抱究。

RSS

Blog
Archives
About

Android實戰(zhàn)技巧:Fragment的那些坑
APR 30TH, 2015 12:40 AM

Fragment是Android在3.0(Homeycomb)版本時加入的用以更靈活的構(gòu)建多屏幕界面的可UI組件恢氯。關(guān)于Fragment以基本使用方法可以參考官方的教程最佳實踐,以及選擇Activity還是Fragment。 但是Fragment使用起來卻遠沒有教程中說的那樣簡單酿雪,也遠比Activity要復(fù)雜一些遏暴,這里總結(jié)了孤在使用Fragment時所遇到的坑。


嵌套Fragment時Duplicated id或者Tag之癢
這是一個小坑指黎,但是初學者很容易遇到,特別是在Fragment之中套有Fragment時州丹,且又是布局中添加子Fragment時更容易遇到醋安。
現(xiàn)象:
Fragment中套有另一個Fragment,當?shù)诙芜M入父Fragment時或者由Fragment創(chuàng)建的界面時會拋異常墓毒,大致意思是子Fragment的Id或Tag重復(fù)了吓揪。如果你在layout中給子fragment加了id或者tag,那么一定會遇到此異常所计。
原因:
在添加Fragment時都可以為Fragment指定一個Id或者Tag用以標識這個Fragment柠辞。因為每個Activity所附帶的Fragment都是放在一個對象池中,在Activity的生命周期里主胧,F(xiàn)ragment仍然在池中叭首,即使是把某一個Fragment從Activity中detach掉(也即用FragmentManager pop掉),這個池是由FragmentManager來管理的踪栋。當你再次要以某個id或者Tag添加Fragment時焙格,F(xiàn)ragmentManager會在池中檢索,如果發(fā)現(xiàn)已經(jīng)存在Fragment對象帶有此Id或者Tag時夷都,就會拋此異常并報怨Id重復(fù)眷唉。這么做的目的就是減少對象的創(chuàng)建,盡可以的復(fù)用對象囤官。
如何破解:
在布局中寫fragment時冬阳,不要添加id或者tag;
如果非要添加id或者tag党饮,就在代碼中添加fragment肝陪,如使用Id或者Tag時,先到FragmentManager中查找對象是否存在劫谅,不存在時再創(chuàng)建见坑,也即:
Fragment target = getFragmentManager().findFragmentByTag("tag"); if (target == null) { targe = new SomeFragment(); } FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(R.id.content, target, "tag"); ft.commit();

replace之痛
現(xiàn)象:
當有二個相同的整體頁面層疊時,想把最后一個布局中的某個用Fragment來replace捏检,會發(fā)現(xiàn)荞驴,它把前面的replace,后面的沒效果贯城。
原因:
布局的Id在一個窗體(Activity)中是唯一的熊楼,F(xiàn)ragment的replace也是使用此唯一的Id來把相應(yīng)布局替換成Fragment的。當相同的頁面層疊時,同一個Id的布局出現(xiàn)了二次鲫骗,但Id是一樣的犬耻。所以FragmentTransaction在replace時僅替換了一個。而不會像期待的那樣执泰,替換最后一個頁面枕磁。
如何破解:
如果相同的頁面非要層疊,要么不使用Fragment术吝,要么為布局設(shè)置不同的Id计济。這種情況多出現(xiàn)在布局的復(fù)用上面,比如某二個頁面長的像排苍,所以復(fù)用了同一整體布局沦寂。但實際的邏輯上不是相同的頁面,完全可以為布局設(shè)置不同的Id淘衙。
可見性之疼
現(xiàn)象:
當有多個Fragment層疊在一起時传藏,每個Fragment如何能感知其對用戶的可見性。比如應(yīng)用有三個頁面彤守,A毯侦,B和C,比如A是整體類別列表遗增,B是每個類別的詳情叫惊,C又是類別的某種更詳細的信息,當C顯示出來時做修,A和B怎么能知道它其實對于用戶已經(jīng)不可見了霍狰,所以就可以不刷新,不加載數(shù)據(jù)等等饰及。當C被用戶BACK后蔗坯,B又如何感覺它變成可見了?
原因:
Fragment的生命周期與Activity是一樣的燎含,添加到Activity會把OnCreate類似的回調(diào)走一遍宾濒,然后,Activity onResume/onPause/onstart/onStop時屏箍,其所持有的Fragment也走相應(yīng)的onResume/onPause/onstart/onPause绘梦。但是Fragment與Activity非常不同的是,Activity當有另一個Activity顯示時赴魁,當前的Activity會走onPause/onStop卸奉,而Fragment則完全沒有感知。最多只能從FragmentManager那里知道BackStackState改變了颖御,但是是Fragment增加了榄棵,還是減少了,并不能知道。
如何破解:
這個一個非常令人蛋疼的問題疹鳄,簡單的頁面還好拧略,但是涉及到數(shù)據(jù)加載或者要針對某些事件(網(wǎng)絡(luò))刷新時就有問題了,對用戶不可見的頁面沒必要刷新瘪弓〉媲可行的解法就是:
監(jiān)聽FragmentManager的BackStackState的改變
定義頁面路徑深度然后與BackStack深度比較,以感知是否對用戶可見 如前面A是一級腺怯,其path為1月褥,B是2,C是3瓢喉。當前Stack深度為3時,C是可見的舀透,A與B不可見栓票,以此類推。

空白區(qū)域的點擊之膿
現(xiàn)象:
一個Fragment愕够,層疊在另外一個Fragment或者Activity之上走贪,此Fragment中有一些空白區(qū)域,也即Widget之外的空白區(qū)域惑芭,當點擊這些空白區(qū)域的時候發(fā)現(xiàn)這個Fragment下面的Fragment或者Activity中的View收到了事件并且響應(yīng)了點擊事件坠狡。
原因:
Fragment的本質(zhì)就是一個View布局的管理器,當Fragment attach到Activity時遂跟,其實就是把Fragment#onCreateView()返回的View逃沿,替換掉(如果是用replace)FragmentTransaction#replace中指定的View,或者添加到(如果是add)FragmentTransaction#add()中指定的ViewGroup里面幻锁。
當我們以層疊方式顯示多個Fragment時凯亮,通常的做法就是弄一個FrameLayout,然后每次把Fragment add到此布局哄尔。因此假消,這時Activity的頁面布局樹實際上就是一個FrameLayout里面包含幾個View。
所以岭接,當點擊上面Fragment的空白區(qū)域時富拗,如果事件沒被吃掉,就會向下傳遞鸣戴。
如何破解:
在Fragment的根布局加上一個clickable=true啃沪,這會讓根布局把點擊事件吃掉,以防止事件會繼續(xù)傳遞下去葵擎,造成上面的情況谅阿。
Activity重新創(chuàng)建之殤
現(xiàn)象:
這個沒有一般性的錯誤,只會有與項目相關(guān)的具體的錯誤異常,或者頁面顯示不正確签餐。以及為什么教程中都有這么一句:

123456

@OverrideonCreate(Bundle savedInstance) { if (savedIntance == null) { // create fragment and add it to Activity. }}

原因:
Activity除了正常啟動走到onCreate寓涨,還有另外的入口,比如系統(tǒng)配置信息發(fā)生變化時氯檐,或者Activity在棧比較深的地方戒良,系統(tǒng)會把Activity殺掉,然后再重新創(chuàng)建它冠摄,問題就是在這個重新創(chuàng)建糯崎。重新創(chuàng)建與新建一個Activity不同,它是要盡可能的恢復(fù)先前所在的狀態(tài)河泳,因為這對用戶來說是透明的沃呢,也就是說不能讓用戶感知到,否則體驗會相當差拆挥。唯一與常規(guī)創(chuàng)建的區(qū)別就在于傳給onCreate的參數(shù)savedInstanceState是不是null.
如何破解:
為了能在Activity重建時恢復(fù)狀態(tài)薄霜,需要:
對于Activity
要在onSaveInstanceState()時,把一些變量保存纸兔,然后在onCreate時恢復(fù)

對于Fragment
告訴系統(tǒng)惰瓜,你想恢復(fù)狀態(tài)Fragment#setRetainInstance(true)。然后汉矿,也在onSavedInstance()中保存狀態(tài)崎坊,在onCreate時恢復(fù)。 這就夠了洲拇,系統(tǒng)會在重新創(chuàng)建Activity時把其所持有的Fragment也創(chuàng)建出來奈揍。所以為什么每個Fragment子類都需要定義一個默認的Constructor。更多的可以參考這篇文章呻待。

FragmentTransaction的異步操作之殤
FragmentTransaction是異步的打月,commit()僅是相當于把操作加入到FragmentManager的隊列,然后FragmentManager會在某一個時刻來執(zhí)行蚕捉,并不是立即執(zhí)行奏篙。所以,真正開始執(zhí)行commit()時迫淹,如果Activity的生命周期發(fā)生了變化秘通,比如走到了onPause,或者走到了onStop敛熬,或者onDestroy都走完了肺稀,那么就會報出IllegalStateException。
還有一個異步的原因就是应民,在異步中操作(顯示)Fragment话原。比如夕吻,先去網(wǎng)絡(luò)請求數(shù)據(jù),然后根據(jù)數(shù)據(jù)顯示一個Fragment繁仁,這個特別容易出現(xiàn)的情況是網(wǎng)絡(luò)請求回來了涉馅,但是Activity已經(jīng)不在了,這時如果commit也會報出IllegalStateException黄虱。
具體的原因稚矿,以及如何避免可以參考大牛的這篇文章
常見的解法就是作者建議的:1. 小心在生命周期中commit 捻浦。2 盡量不要在異步回調(diào)中commit 另外的解法 就是
在異步回調(diào)中判斷Activity是否在銷毀中晤揣,isFinishing,如果true朱灿,就停止做其他事情
盡可能把異步任務(wù)控制在活動的生命周期內(nèi)(onStart->onStop)昧识。當出現(xiàn)stop時終止異步任務(wù)。再次start時再次啟動盗扒。
但是這個并不適用所有情況滞诺。比如按HOME的情況,通常這個過程不需要把任務(wù)停掉环疼。因為一般情況下,再切回來時朵耕,應(yīng)用應(yīng)該保持切走時的狀態(tài)炫隶,比如,加載一個數(shù)據(jù)阎曹,按HOME切走伪阶,再回來時,應(yīng)該加載完成处嫌。這也正是多任務(wù)系統(tǒng)的一個表現(xiàn)栅贴。 如果onstop時停掉任務(wù),那么要做很多工作來在onstart時恢復(fù)狀態(tài)檐薯。

使用commitAllowStateLoss() 這個是最終方案捆昏。除了從設(shè)計 上避免以外左胞,這是唯 一的方式敛摘。

惡心的Activity重建以及恢復(fù)其Fragment
首先說安卓系統(tǒng)非常惡心的一點就是某些情況下系統(tǒng)會殺掉Activity屯远,然后重新創(chuàng)建并嘗試恢復(fù)其先前的狀態(tài)泄私,比如當旋轉(zhuǎn)屏幕時,當系統(tǒng)語言發(fā)生變化時,當棧中的Activity被回收了,又到棧頂時等等,這點非常惡心,常常帶來問題。識別重建與新建的方法就是看onCreate中的Bundle參數(shù)是不是null。
對于FragmentActivity装悲,更加惡心阅嘶,此種場景時魂迄,它在onSaveInstance時會保存Fragment,然后在onCreate時會重新創(chuàng)建,會調(diào)用Framgment的默認無參構(gòu)造來創(chuàng)建Fragment對象美莫。所以這也是為什么文檔中說Fragment一定要有一個默認的構(gòu)造函數(shù)傀顾,而且最好不要有帶參數(shù)的構(gòu)造函數(shù)哩都,傳參數(shù)要用setArguments化撕。默認構(gòu)造函數(shù)的原因是為了重建Fragment實例。setArguments的參數(shù)是一個Bundle也會跟隨Fragment保存起來圾浅,在重建Fragment時會幫你恢復(fù)果复。這里的恢復(fù)狀態(tài)的數(shù)據(jù)的保存都是通過Binder方式保存在系統(tǒng)中,這也說明為啥參數(shù)非要是一個Bundle陌僵。
那么問題來了胎撤,當你確實需要帶參數(shù)的構(gòu)造函數(shù),或者說系統(tǒng)無法幫你重建Fragment(比如Fragment要從動態(tài)加載的Dex中獲雀)時怎么辦呢魁衙?
首先,我們要模擬這一場景株搔,最方便的就是把activity的configChanges去掉剖淀,然后旋轉(zhuǎn)屏幕。
一個思路就是阻止系統(tǒng)恢復(fù)Fragment纤房,我們可以自己來加載纵隔,因為重建也會走到Activity的onCreate,所以我們有理由重走一遍初始化流程炮姨。怎么阻止呢捌刮,就是在FragmentActivity保存所有Fragment狀態(tài)前把Fragment從FragmentManager中移除掉。

1234567

@Overridepublic void onSaveInstance(Bundle out) { FragmentTransaction ft = getSupportFragmentManager().benginTransaction(); ft.remove(frag); ft.commitAllowStateLoss(); super.onSaveInstance(out);}

參考
Fragments
Building a Dynamic UI with Fragments
Fragment Transactions & Activity State Loss
Handling Configuration Changes with Fragments

Posted by Alex Hilton Apr 30th, 2015 12:40 am
effectiveandroid
1

? Android NDK開發(fā)的一些技巧CSS Animation初探 ?

Comments
最新最早最熱
4條評論
1條新浪微博


solidajun
期待作者更多的精彩文章~
[給力]

1月26日回復(fù)頂轉(zhuǎn)發(fā)


AndWang
第一個應(yīng)該是target你寫成了targe舒岸,嘿嘿
4月21日回復(fù)頂轉(zhuǎn)發(fā)


風語安然
非常非常感謝I鹱鳌!蛾派!解決了我的大問題俄认。 空白區(qū)域的點擊之膿,哈哈哈
5月8日回復(fù)頂轉(zhuǎn)發(fā)


蔣朋
以為來到了小黑屋洪乍,哈哈眯杏,不錯哦
5月30日回復(fù)頂轉(zhuǎn)發(fā)

社交帳號登錄:
微信
微博
QQ
人人
更多?

發(fā)布

稀有猿訴正在使用多說

About Me
實用主義的浮躁碼農(nóng)
專注移動互聯(lián)網(wǎng),五年Android開發(fā)經(jīng)驗壳澳,也關(guān)注iOS開發(fā)和WEB開發(fā)

Recent Posts
說說Android的MVP模式
Android技巧:學習使用GridLayout
安卓開發(fā)技術(shù):監(jiān)聽軟鍵盤的顯示與隱藏
Android應(yīng)用性能剖析全攻略
Android Studio技能之快捷鍵

更多的文章
浪人的星空

Categories
miscellaneous
effectiveandroid
android
ios
java
web

GitHub Repos
pandoraCrawler to get hot movies

MoreEffectiveAndroidExamples of Android App Development

alexhilton.github.io

miscellaneousMiscellaneous code samples when learning new languages

cocoajourneyThe projects with Cocoa Touch

effectivecocoaAPI demos and tutorials about Cocoa Touch

EffectiveJavaSample codes for Java SE

EffectiveAndroidExamples and exercises and tutorials when learning Android Development.

gitatouilleGit manuals, Git tutorials, articles about Git and Git tricks. All in an app.

vulcanAndroid sample app with iOS style and theme including bottom tabs and header navigation bar

@alexhilton on GitHub

Copyright ? 2016 - Alex Hilton - Powered by Octopress

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岂贩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巷波,更是在濱河造成了極大的恐慌河闰,老刑警劉巖科平,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姜性,居然都是意外死亡瞪慧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門部念,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弃酌,“玉大人,你說我怎么就攤上這事儡炼〖讼妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵乌询,是天一觀的道長榜贴。 經(jīng)常有香客問我,道長妹田,這世上最難降的妖魔是什么唬党? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鬼佣,結(jié)果婚禮上驶拱,老公的妹妹穿的比我還像新娘。我一直安慰自己晶衷,他們只是感情好蓝纲,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晌纫,像睡著了一般税迷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锹漱,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天箭养,我揣著相機與錄音,去河邊找鬼凌蔬。 笑死,一個胖子當著我的面吹牛闯冷,可吹牛的內(nèi)容都是我干的砂心。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛇耀,長吁一口氣:“原來是場噩夢啊……” “哼辩诞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纺涤,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤译暂,失蹤者是張志新(化名)和其女友劉穎抠忘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體外永,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡崎脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了伯顶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囚灼。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祭衩,靈堂內(nèi)的尸體忽然破棺而出灶体,到底是詐尸還是另有隱情,我是刑警寧澤掐暮,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布蝎抽,位于F島的核電站,受9級特大地震影響路克,放射性物質(zhì)發(fā)生泄漏樟结。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一衷戈、第九天 我趴在偏房一處隱蔽的房頂上張望狭吼。 院中可真熱鬧,春花似錦殖妇、人聲如沸刁笙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疲吸。三九已至,卻和暖如春前鹅,著一層夾襖步出監(jiān)牢的瞬間摘悴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工舰绘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蹂喻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓捂寿,卻偏偏與公主長得像口四,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秦陋,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

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