以下堆棧信息日志是常見的illegalStateExecption日志。筆者是當(dāng)應(yīng)用出于后臺時,網(wǎng)絡(luò)響應(yīng)更新fragment時出現(xiàn)以下問題体箕。本文將對該異常拋出的的時間和原因進(jìn)行解釋,并對如何避免異常的發(fā)生提出一些意見。
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
異常出現(xiàn)的原因
根據(jù)日志消息脾歇,顧名思義,該異常發(fā)生的原因是在Activity的state已經(jīng)被保存之后仍然試圖調(diào)用commit去提交FragmentTransaction. 在詳細(xì)解析這句話真正意思之前淘捡,讓我們首先了解一下究竟onSaveInstance里做了什么藕各。安卓系統(tǒng)隨時可能殺死進(jìn)程來釋放內(nèi)存,后臺Activity可能隨時被殺死焦除。因此Framework通過調(diào)用onSaveInstanceState( ) 來保存activity的狀態(tài)State. FrameWork通過Bundle對象來保存state, 其中記錄下dialog,fragment,view等視圖結(jié)構(gòu)和狀態(tài)激况。這樣即便Activity因?yàn)楫惓G闆r被系統(tǒng)殺死,還是可以通過保存下了的狀態(tài)來恢復(fù)原來的視圖膘魄。
了解了onSaveInstance具體做了什么之后乌逐,再來討論為什么onSaveInstance被調(diào)用之后不能調(diào)用commit.原因就是,onSaveInstance已經(jīng)用Bundle對象將Activity所有狀態(tài)視圖信息保存下來了创葡,這時候想要把這個事務(wù)提交上去是不可能的浙踢,安卓開發(fā)團(tuán)隊(duì)出于保護(hù)用戶界面不被影響,不惜一切代價防止保存的狀態(tài)不丟失灿渴,因此采用拋出異常的方式來解決洛波。聽起來有點(diǎn)抽象胰舆,其實(shí)可以通過一個例子來理解,全班同學(xué)的卷子都已經(jīng)上交并且批改結(jié)束蹬挤,分?jǐn)?shù)排名都出來了缚窿。這時候你再提交卷子肯定是不行的,誰知道你有沒有偷抄焰扳,如果你偷抄的話豈不是影響了班級的排名倦零。
什么時候會拋出異常
如果你之前已經(jīng)遇到過這樣的異常,可能已經(jīng)注意到在不同的安卓版本上異常拋出的可能性不一樣吨悍。例如扫茅,老的版本拋出異常的概率更小畜份;或者如果應(yīng)用程序使用support library時拋出異常的概率更大诞帐。這會讓人覺得是不是support library 有問題。然而事實(shí)上不是如此爆雹。
拋出異常的概率不同主要原因是Android3.0(HoneyComb)其之后版本中Activity生命周期的顯著差異造成的停蕉。在3.0以前,onSaveInstance是在onPause調(diào)用之前調(diào)用钙态,而3.0以后onSaveInstance是在onStop調(diào)用之前調(diào)用的慧起,這樣的話3.0以前在onSaveInstance之后調(diào)用commit的概率更高了,因此異常發(fā)生的概率也就更高了册倒。
如何避免異常發(fā)生
當(dāng)理解了異常發(fā)生背后的真相之后蚓挤,如何避免就輕而易舉了。
-
在Activity生命周期中調(diào)用commit一定要注意驻子。
盡量只在onCreate中調(diào)用commit.如果冒險(xiǎn)在其他生命周期回調(diào)函數(shù)中調(diào)用commit,例如onActivityResult(),onStart(),onResume,就很有可能出問題灿意。例如在onResume中調(diào)用commit,由于此時activity的狀態(tài)信息還沒有恢復(fù),就會拋出異常崇呵。如果一定要在其他生命周期中調(diào)用commit,可以在onResumeFragments或者在onPostResume中調(diào)用缤剧。這兩個方法可以保證activity的狀態(tài)信息已經(jīng)恢復(fù)了。 -
避免在異步回調(diào)中調(diào)用commit.
包括AsyncTask.onPostExecute()和LoaderManager.LoaderCallbacks.onLoadFinished().因?yàn)樵谶@些回調(diào)中調(diào)用commit時域慷,不知道當(dāng)前activity的生命周期走到哪一步荒辕,不知道是否當(dāng)前Activity的狀態(tài)信息是否已經(jīng)被保存。下面通過一個事件序列來說明:
1.一個Activity執(zhí)行了一個AsyncTask.
2.點(diǎn)擊Home鍵犹褒,此時onSaveInstanceState和onStop被調(diào)用
3.AsyncTask完成調(diào)用onPostExecute,但是并不知道此時activity已經(jīng)結(jié)束
4.在onPostExcute中調(diào)用commit,將導(dǎo)致異常拋出
總的來說抵窒,避免異常的最好方法是避免在異步回調(diào)中調(diào)用commit。 - 使用 commitAllowingStateLoss()
commit和commitAllowingStateLoss的不同之處在于后者不會拋出異常叠骑,即使?fàn)顟B(tài)發(fā)生丟失李皇。通常情況下不應(yīng)該使用這個方法,因?yàn)檫@會導(dǎo)致activity的狀態(tài)信息丟失座云。因此最好的方法還是在保證activity的狀態(tài)信息被保存之前調(diào)用commit.