下面的堆棧跟蹤和異常代碼克伊,自從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ù)辜妓。