Fragment Transactions和Activity狀態(tài)丟失

下面的堆棧跟蹤和異常代碼克伊,自從Honeycomb的初始發(fā)行版本就一直使得StackOverflow很迷惑。

java.lang.IllegalStateException:CannotperformthisactionafteronSaveInstanceState

atandroid.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)

atandroid.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)

atandroid.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)

atandroid.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

這篇博客將會(huì)解釋,這個(gè)異常在什么時(shí)候發(fā)生以及為什么會(huì)發(fā)生?并且提供幾種方法讓這種異常不會(huì)發(fā)生在你的應(yīng)用中憨奸。

為什么會(huì)拋出這個(gè)異常?

這種異常的出現(xiàn)是由于凿试,在Activity的狀態(tài)保存之后排宰,嘗試去提交一個(gè)FragmentTransaction。這種現(xiàn)象被稱為活動(dòng)狀態(tài)丟失(Activity State Loss)那婉。然而板甘,在我們了解這種異常的真正含義之前,讓我們先看看當(dāng)onSaveInstanceState()函數(shù)被調(diào)用的時(shí)候到底發(fā)生了什么详炬。

正如最近我在關(guān)于Binders & Death Recipients博客里面討論的那樣盐类,Android應(yīng)用在Android運(yùn)行環(huán)境里很難決定自己的命運(yùn)。Android系統(tǒng)可以在任何時(shí)候通過(guò)結(jié)束一個(gè)進(jìn)程以釋放內(nèi)存,而且background activities可能在沒(méi)有任何警告的情況下被清理在跳。為了確保這種不確定的行為對(duì)于用戶是透明的枪萄,在Activity可以銷毀之前,通過(guò)調(diào)用onSaveInstanceState()方法猫妙,架構(gòu)給每個(gè)Activity一個(gè)保存自身狀態(tài)的機(jī)會(huì)瓷翻。在重新加載已保存的狀態(tài)時(shí),對(duì)于foreground和background Activities的切換割坠,為用戶帶來(lái)了無(wú)縫切換的體驗(yàn)逻悠。用戶不用去關(guān)心這個(gè)Activity是否被系統(tǒng)銷毀了韭脊。

在框架調(diào)用onSaveInstanceState()方法時(shí)童谒,給這個(gè)方法傳遞了一個(gè)Bundle對(duì)象。Activity可以通過(guò)這個(gè)對(duì)象來(lái)存儲(chǔ)它的狀態(tài)沪羔,而且Activity把它的dialogs饥伊、fragments以及views的狀態(tài)都保存在這個(gè)對(duì)象里面。當(dāng)這個(gè)函數(shù)返回時(shí)蔫饰,系統(tǒng)打包這個(gè)Bundle對(duì)象通過(guò)一個(gè)Binder接口傳遞給系統(tǒng)服務(wù)處理琅豆,然后它會(huì)被安全的存儲(chǔ)下來(lái)。當(dāng)系統(tǒng)決定重新創(chuàng)建這個(gè)Activity的時(shí)候篓吁,它會(huì)給這個(gè)應(yīng)用傳回一個(gè)相同的Bundle對(duì)象茫因,通過(guò)這個(gè)對(duì)象可以重新裝載Activity銷毀時(shí)的狀態(tài)。

那為什么會(huì)拋出這個(gè)異常呢杖剪?這個(gè)問(wèn)題源于這樣的事實(shí)冻押,Bundle對(duì)象代表一個(gè)Activity在調(diào)用onSaveInstanceState()方法的一個(gè)瞬間快照,僅此而已盛嘿。這意味著洛巢,當(dāng)你在onSaveInstanceState()方法調(diào)用后會(huì)調(diào)用FragmentTransaction的commit方法。這個(gè)transaction將不會(huì)被記住次兆,因?yàn)樗鼪](méi)有在第一時(shí)間記錄為這個(gè)Activity的狀態(tài)的一部分稿茉。從用戶的角度來(lái)看,這個(gè)transaction將會(huì)丟失芥炭,可能導(dǎo)致UI狀態(tài)丟失漓库。為了保證用戶的體驗(yàn),Android不惜一切代價(jià)避免狀態(tài)的丟失园蝠。因此渺蒿,無(wú)論什么時(shí)候發(fā)生,都將簡(jiǎn)單的拋出一個(gè)IllegalStateException異常砰琢。

什么時(shí)候會(huì)拋出這個(gè)異常蘸嘶?

如果之前你遇到過(guò)這個(gè)異常良瞧,也許你已經(jīng)注意到異常拋出的時(shí)間在不同的版本平臺(tái)有細(xì)微的差別陪汽。也許你會(huì)發(fā)現(xiàn)训唱,老版本的機(jī)器拋出異常的頻率更低,或者你的應(yīng)用使用Support Library比使用官方的框架類的時(shí)候更容易拋出異常挚冤。這個(gè)細(xì)微的區(qū)別已經(jīng)導(dǎo)致一些人在猜測(cè)Support Library有bug况增,是不值得相信的。然而训挡,這樣的猜想完全錯(cuò)誤澳骤。

這些細(xì)微區(qū)別存在的原因是源于Honeycomb上對(duì)于Activity生命周期所做的巨大改變。在Honeycomb之前澜薄,Activity直到暫停后才考慮被銷毀为肮。這意味著在onPause()方法之前onSaveInstanceState()方法被立即調(diào)用。然而肤京,從Honeycomb開(kāi)始颊艳,考慮銷毀Activity只能是在他們停止之后,這意味著onSaveInstanceState()方法現(xiàn)在是在onStop()方法之前調(diào)用忘分,以此代替在onPause()方法之前調(diào)用棋枕。這些不同總結(jié)如下表:


作為Activity生命周期已做的細(xì)微改變的結(jié)果,Support Library有時(shí)候需要根據(jù)平臺(tái)的版本來(lái)改變它的行為妒峦。比如重斑,在Honeycomb及以上的設(shè)備中,每當(dāng)一個(gè)commit方法在onSaveInstanceState()方法之后調(diào)用時(shí)肯骇,都會(huì)拋出一個(gè)異常來(lái)提醒開(kāi)發(fā)者狀態(tài)丟失發(fā)生了窥浪。然而,在Honeycomb之前的設(shè)備上笛丙,每次它發(fā)生時(shí)并拋出異常將更受限制寒矿,他們的onSaveInstanceState()方法在Activity的生命周期中更早調(diào)用,結(jié)果更容易發(fā)生狀態(tài)丟失若债。Android團(tuán)隊(duì)被迫做了一個(gè)折中的辦法:為了更好的與老版本平臺(tái)交互符相,老的設(shè)備不得不接受偶然狀態(tài)丟失可能發(fā)生在onPause()方法和onStop()方法之間。Support Library在不同平臺(tái)的行為總結(jié)如下表:

如何避免拋出異常蠢琳?

一旦你了解了到底發(fā)生了什么啊终,避免發(fā)生Activity狀態(tài)丟失將會(huì)很簡(jiǎn)單。如果你讀了這篇博客傲须,那么很幸運(yùn)你更好的了解了Support Library是怎么工作的蓝牲,以及在你的應(yīng)用中避免狀態(tài)丟失為什么如此的重要。假如你查看這個(gè)博客是為了查找快速解決的辦法泰讽,那么例衍,當(dāng)你在你的應(yīng)用中使用FragmentTransactions的時(shí)候昔期,應(yīng)牢記以下的這些建議:

建議一

當(dāng)你在Activity生命周期函數(shù)里面提交transactions的時(shí)候要小心。大部分的應(yīng)用僅僅在onCreate()方法被調(diào)用的開(kāi)始時(shí)間提交transactions佛玄,或者在相應(yīng)用戶輸入的時(shí)候硼一,因此將不可能碰到任何問(wèn)題。然而梦抢,當(dāng)你的transactions在其他的Activity生命周期函數(shù)提交厌秒,如onActivityResult()策彤、onStart()和onResume()似谁,事情將會(huì)變得微妙构拳。例如,你不應(yīng)該在FragmentActivity的onResume()方法中提交transactions霞赫。因?yàn)橛行r(shí)候這個(gè)函數(shù)可以在Activity的狀態(tài)恢復(fù)前被調(diào)用(可以查看相關(guān)文檔了解更多信息)腮介。如果你的應(yīng)用要求在除onCreate()函數(shù)之外的其他Activity生命周期函數(shù)中提交transaction,你可以在FragmentActivity的onResumeFragments()函數(shù)或者Activity的onPostResume()函數(shù)中提交端衰。這兩個(gè)函數(shù)確保在Activity恢復(fù)到原始狀態(tài)之后才會(huì)被調(diào)用叠洗,從而避免了狀態(tài)丟失的可能性。(示例:看看我對(duì)this StackOverflow question的回答靴迫,來(lái)想想如何提交FragmentTransactions作為Activity的onActivityResult方法被調(diào)用的響應(yīng))惕味。

建議二

避免在異步回調(diào)函數(shù)中提交transactions。包括常用的方法玉锌,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法名挥。在這些方法中執(zhí)行transactions的問(wèn)題是,當(dāng)他們被調(diào)用的時(shí)候主守,他們完全沒(méi)有Activity生命周期的當(dāng)前狀態(tài)禀倔。例如,考慮下面的事件序列:

一個(gè)Activity執(zhí)行一個(gè)AsyncTask参淫。

用戶按下“Home”鍵救湖,導(dǎo)致Activity的onSaveInstanceState()和onStop()方法被調(diào)用。

AsyncTask完成并且onPostExecute方法被調(diào)用涎才,而它沒(méi)有意識(shí)到Activity已經(jīng)結(jié)束了鞋既。

在onPostExecute函數(shù)中提交的FragmentTransaction,導(dǎo)致拋出一個(gè)異常耍铜。

一般來(lái)說(shuō)邑闺,避免這種類型異常的最好辦法就是不要在異步回調(diào)函數(shù)中提交transactions。Google工程師似乎同意這個(gè)信條棕兼。根據(jù)Android Developers group上的這篇文章,Android團(tuán)隊(duì)認(rèn)為UI主要的改變陡舅,源于從異步回調(diào)函數(shù)提交FragmentTransactions引起不好的用戶體驗(yàn)。如果你的應(yīng)用需要在這些回調(diào)函數(shù)中執(zhí)行transaction而沒(méi)有簡(jiǎn)單的方法可以確保這個(gè)回調(diào)函數(shù)不好在onSaveInstanceState()之后調(diào)用伴挚。你可能需要訴諸于使用commitAllowingStateLoss方法靶衍,并且處理可能發(fā)生的狀態(tài)丟失灾炭。(可以看看StackOverflow上的另外兩篇文章,這一篇另一篇)颅眶。

建議三

作為最后的辦法蜈出,使用commitAllowingStateLoss()函數(shù)。commit()函數(shù)和commitAllowingStateLoss()函數(shù)的唯一區(qū)別就是當(dāng)發(fā)生狀態(tài)丟失的時(shí)候帚呼,后者不會(huì)拋出一個(gè)異常掏缎。通常你不應(yīng)該使用這個(gè)函數(shù)皱蹦,因?yàn)樗馕犊赡馨l(fā)生狀態(tài)丟失煤杀。當(dāng)然,更好的解決方案是commit函數(shù)確保在Activity的狀態(tài)保存之前調(diào)用沪哺,這樣會(huì)有一個(gè)好的用戶體驗(yàn)沈自。除非狀態(tài)丟失的可能無(wú)可避免,否則就不應(yīng)該使用commitAllowingStateLoss()函數(shù)辜妓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枯途,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子籍滴,更是在濱河造成了極大的恐慌酪夷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽惰,死亡現(xiàn)場(chǎng)離奇詭異晚岭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勋功,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)坦报,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人狂鞋,你說(shuō)我怎么就攤上這事片择。” “怎么了骚揍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵字管,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我信不,道長(zhǎng)嘲叔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任浑塞,我火速辦了婚禮借跪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酌壕。我一直安慰自己掏愁,他們只是感情好歇由,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著果港,像睡著了一般沦泌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辛掠,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天谢谦,我揣著相機(jī)與錄音,去河邊找鬼萝衩。 笑死回挽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猩谊。 我是一名探鬼主播千劈,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牌捷!你這毒婦竟也來(lái)了墙牌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤暗甥,失蹤者是張志新(化名)和其女友劉穎喜滨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撤防,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虽风,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了即碗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰情。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖剥懒,靈堂內(nèi)的尸體忽然破棺而出内舟,到底是詐尸還是另有隱情,我是刑警寧澤初橘,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布验游,位于F島的核電站,受9級(jí)特大地震影響保檐,放射性物質(zhì)發(fā)生泄漏耕蝉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一夜只、第九天 我趴在偏房一處隱蔽的房頂上張望垒在。 院中可真熱鬧,春花似錦扔亥、人聲如沸场躯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踢关。三九已至伞鲫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間签舞,已是汗流浹背秕脓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儒搭,地道東北人吠架。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像师妙,于是被迫代替她去往敵國(guó)和親诵肛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屹培,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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