IllegalStateException: Can not perform this action after onSaveInstanceState

下面自從Honeycomb發布后,下面棧跟蹤信息和異常信息已經困擾了StackOverFlow很久了。
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState atandroid.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager. at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager. at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord. at android.support.v4.app.BackStackRecord.commit(BackStackRecord.

這篇文章會解釋這個異常什么時候會拋出以及原因,并且會以一些建議收尾。這些建議會幫助你不會因為這個異常導致程序崩潰。

**
異常原因:Fragment提交transaction導致state loss異常
處理方法:
?在FragmentTransactions調用的commit()方法替換成commitAllowingStateLoss()方法即可。(在onCreate()或者為了響應用戶輸入的時候調用一次commit()不會拋這個崩潰)
?**

這個異常為什么會拋出?

這個異常拋出的原因是因為你嘗試著在Activity的狀態已經保存后commit一個FragmentTransaction,導致了一個現象叫做Activity state loss。在我們深入細節之前,讓我們先看看在onSaveInstanceState()調用后發生了什么。Android應用程序在Android 運行時系統中只有很小的控制權。Android系統為了釋放內存可以在任意時刻停止進程,然后處于后臺的Activity就會被毫無警告地殺掉。為了保證有時候因此引起的不穩定行為能避免用戶知道,Android框架給每一個Activity通過調用onSaveInstanceState()來保存自己狀態的機會,它會在Activity可能被銷毀之前調用。當后面恢復狀態的時候,用戶不會感覺到Activity已經被系統殺掉了,而會感覺前臺和后臺的Activity無縫切換。

當Android框架調用onSaveInstanceState(),它將一個Bundle對象通過這個方法傳遞,以便Activity后面恢復狀態。Activity可以將它的Dialog、fragment以及view的狀態保存在Bundle中。當這個方法返回的時候,系統通過Binder結果打包Bundle對象然后傳給系統服務進程。系統服務進程負責保證Bundle對象安全地保存下來。當系統后面決定重新創建Activity的獲釋后,它就會將相同的Bundle對象發揮應用程序,以便于用它來回復舊的Activity狀態。

所以為什么這個異常隨后拋出?這個問題導致的原因是因為那些Bundle對象代表Activity在onSaveInstanceState()被調用那時候的一個快照,沒更多了。這就意味著當你在onSaveInstanceState()之后調用FragmentTransaction#commit()的時候,transation不會被記錄。因為它不會作為之前Activity的狀態被保存。從用戶的角度來說,這個transaction就像丟失了,導致UI狀態意外的丟失。為了保證用戶體驗,Android不計一切代價避免狀態丟失,也就是當它發生的時候簡單地拋出一個IllegalStateException。

這個異常什么時候會拋出?

如果你之前已經碰到過這個異常,你可能會注意到異常拋出的時機因為不同的Android版本而不一致。比如,如可能會發現老版本的設備上,這個異常拋出比較不頻繁,或者當你的程序中使用support library而不是官方框架中的類時更容易觸發這個異常。這些輕微的一致讓很多人都以為support library有bug,不值得信任。然而,這些假設都不是正確的。

這些輕微的不一致是因為在Honeycomb版本中的Activity生命周期有了重要的變化。Honeycomb之前的版本,activity被認為在pause之前都不會被殺掉,這意味著onSaveInstanceState()會在onPause()之前被調用。從HoneyComb開始,Activity被認為只會在stopped只會被殺掉,意味著onSaveInstanceState()現在會在onStop()之前被調用而不是在onPause()之前。這些變化在下表中總結:

Honeycomb之前 Honeycomb之后
Activity是否可以在onPause()之前被殺掉? NO NO
Activity是否可以在onStop()之前被殺掉? YES NO
onSaveInstanceState(Bundle) 保證在...之前被調用 onPause() onStop()

由于Activity生命周期的輕微變化,support library有時候需要根據系統版本選擇他的行為。比如,在Honeycomb及以上設備,每次在onSaveInstanceState()之后調用commit()都會拋出一個異常,以便警告開發者已經發生了狀態丟失。然而,在每次這種情況拋出異常在Honeycomb之前的設備上就顯得太具有限制性了,它們的onSaveInstanceState()調用發生在Activity生命周期中更早的一段時期,并且更容易導致意外的狀態丟失。Android團隊被迫做出妥協:為了更好地跟老版本兼容,舊設備可能必須要忍受在onPause()和onStop()之間意外的狀態丟失。Support library在不同兩個版本的行為如下表總結:

Honeycomb之前 Honeycomb之后
commit在onPause()之前 OK OK
commit在onPause() 和onStop()之間 STATE LOSSOK
commit在onStop()之后 EXCEPTION EXCEPTION

怎么避免這個異常?

一旦你懂得了真正發生了什么,避免Activity狀態丟失就簡單多了。如果你已經在讀這篇文章之間就已經解決過這個問題了,希望你能對support library有一個更深的了解,并且知道為什么避免狀態丟失對你的程序這么重要。為了方便你通過這篇文章尋找快速的解決方案,這里有一些建議希望你記得在使用FragmentTransactions的時候使用:

在Activity生命周期方法中commit transation的時候一定要小心。

很多應用程序只會在onCreate()或者為了響應用戶輸入的時候調用一次,所以他們不會遇到任何問題。然而,當你的transation開始冒險在其他的生命周期(比如onActivityResult(),onStart(),onResume() )中commit的時候,事情就可能變得棘手了。比如,你不應該在FragmentActivity#onResume() 方法中commit transation,為了避免有些時候這個方法在Activity的狀態恢復之前被調用( 查看文檔,了解更多)。如果你的應用程序需要在處理onCreate()之外的生命周期方法中commit transation,在FragmentActivity#onResume() 或者Activity#onPostResume()中調用。這兩個方法會被保證在Activity恢復它的狀態之后調用,因此會避免可能的狀態丟失。

避免是異步調用方法中執行transactions。

這個包括經常被使用的方法比如AsyncTask#onPostExecute() 和LoaderManager.LoaderCallbacks#onLoadFinished() 。在這些方法中執行transactions會有問題,因為他們當這些方法被回調的時候,他們不知道Activity當前的生命周期。比如,考慮下面的事件序列:

一個Activity執行一個AsyncTask
用戶按下Home鍵,導致這個Activity的onSaveInstanceState()和onStop() 方法被回調。
AsyncTask完成然后onPostExecute()被調用,而不知道Activity已經處于stopped狀態。
在onPostExectute()方法中的FragmentTransaction被committed,導致一個異常被拋出。

總之,在這些案例中避免異常拋出的最優方法就是避免在異步回調方法中commit transactions。Google工程師似乎同意這個見解。根據在Android Develop group上的這篇文章,Android開發團隊認為通過commit FragmentTransactions來讓UI產生重大的變化對用戶體驗十分不友好。如果你的應用程序需要在這些回調方法中執行transaction,那么沒有什么簡單方法可以保證這些回調不會再onSaveInstanceState()后調用,你可能必須使用commitAllowStateLoss()并且處理可能發生的狀態丟失。(詳見兩篇StackOverFlow文章,文章1文章2)

只使用commitAllowingStateLoss()

作為最后的解決方案。commit()和commitAllowingStateLoss()唯一的區別是后者在狀態丟失的時候不會拋出異常。通常你不會想使用這個方法因為它意味著狀態丟失可能發生。更好的解決方案當然是修改你的程序以便commit()被保證在activity的狀態被保存前調用,因為這樣可能會讓用戶體驗更好。除非狀態丟失是不可避免的,否則commitAllowingStateLoss()就不應該被使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容