IllegalStateException: Can not perform this action after onSaveInstanceState

下面自從Honeycomb發(fā)布后,下面棧跟蹤信息和異常信息已經(jīng)困擾了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調(diào)用的commit()方法替換成commitAllowingStateLoss()方法即可。(在onCreate()或者為了響應(yīng)用戶輸入的時候調(diào)用一次commit()不會拋這個崩潰)
?**

這個異常為什么會拋出?

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

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

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

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

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

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

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

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

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

怎么避免這個異常?

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

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

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

避免是異步調(diào)用方法中執(zhí)行transactions。

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

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

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

只使用commitAllowingStateLoss()

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容