StackOverflow
的一個問題:
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)
解釋為什么會拋出這個異常,并且給出解決這個問題的幾個建議
為什么拋出這個異常
拋出這個異常的原因是試圖在activity
的狀態已經保存之后提交FragmentTransaction
,導致activity
狀態丟失. 在了解詳情之前先來看一看在調用onSaveInstanceState()
都做了什么,在Binders & Death Recipients
文章中討論了Android
應用運行時不好控制,android
系統通過殺死進程釋放內存,后臺運行的activity
可能在毫無征兆的情況下被殺死. 為了確保用戶的難以預測的行為被隱藏,在activity
被銷毀前調用onSaveInstanceState()
方法保存activity
的狀態. 不管當前的activity
有沒有被系統殺死,用戶能在前臺和后臺的activity
中無縫連接
調用onSaveInstanceState()
時,通過Bundle
保存activity
的狀態,activity
的記錄了dialogs
,fragments
,views
狀態,系統通過代理bundle
與服務器端的接口交互,安全的保存了當前的狀態,當系統之后決定恢復activity
,它把相同的bundle
對象返回給應用,通過bundle
恢復之前activity
的狀態
那么為什么會拋出異常呢?問題源于這個bundle
對象在onSaveInstanceState()
被調用時記錄了當前的activity
,也就意味著當前在onSaveInstanceState()
調用之后調用FragmentTransaction#commit()
,這個處理沒有被記住,沒有作為activity
的狀態記錄
這個處理將丟失來自用戶對控件的操作,導致UI的狀態丟失. Android
為了避免狀態丟失拋出了IllegalStateException
異常
什么時候拋出這個異常
如果之前遇到這個問題的話,可能注意到不同的版本拋出的異常有所不同. 越老的設備拋出這個異常的越頻繁,使用support
包比使用官方的包出現更多的異常
引起這個問題的原因是在3.1時改變了activity
的生命周期,在3.1之前沒有考慮到activity
在被暫停之后被殺,意味著onPause()
之前調用了onSaveInstanceState()
方法. 在3.1時,只考慮了activity
在被停止的時候被殺,意味著onSaveInstanceState()
在onStop()
方法之前被調用,而不是在onPause()
之前調用,這個不同點在下表中給出總結:
這個小的改變源于activity
的生命周期,support
包需要根據版本改變行為,在3.1及以上版本,這個異常將在onSaveInstanceState()
方法被調用之后,調用commit()
方法拋出的,告訴開發者activity
的狀態已丟失. 在3.1之前拋出這個異常是限制版本,onSaveInstanceState()
方法的調用早于activity
的生命周期,結果導致當前的狀態丟失. android
團隊做了一個兼容,老設備在onPause()
和onStop()
之間出現狀態丟失,下表給出support
包跨版本的情況:
如何避免這個異常
明白什么原因導致狀態丟失后解決這個問題就變得更加容易,希望明白一點support
包的工作原理,為什么要在應用中避免狀態丟失是至關重要的,這里給出一些建議保證在應用使用FragmentTransactions
正常返回:
- 在
activity
的生命周期內謹慎的使用commit()
方法,大多數應用只有在第一次onCreate()
時或者響應用戶操作時調用commit
,不會拋出這個異常,但是在其他生命周期中使用時可能會拋出這個異常,如onActivityResult()
,onStart()
,onResume()
,不應該在FragmentActivity#onResume()
中commit
,在activity
的狀態恢復之前已經調用了這個方法(官網解讀),如果應用中不在onCreate()
中commit
,可以在FragmentActivity#onResumeFragments()
或者Activity#onPostResume()
中commit
,這兩個方法都是在activity
的狀態恢復之后調用,避免了activity
的狀態丟失 - 避免在接口回掉中執行操作,接口回掉例如
AsyncTask#onPostExecute()
,LoaderManager.LoaderCallbacks#onLoadFinished()
,在這些方法中執行操作會導致狀態丟失,例如下面這個事件:- 在
activity
中使用AsyncTask
- 點擊
home
鍵,導致activity
的onSaveInstanceState()
和onStop()
被調用 -
AsyncTask#onPostExecute()
被調用時,當前的activity
已停止 - 在
onPostExecute()
中commit
,拋出異常
- 在
正常情況下,避免這個異常的最好方式就是避免在回掉中commit
,如果應用中一定要在回掉中用的話,不可避免的會調用onSaveInstanceState()
,可以使用commitAllowingStateLoss()
來解決這個問題
- 使用
commitAllowingStateLoss()
方法作為最后的招數,commit()
和commitAllowingStateLoss()
不同點在于在狀態丟失時后者不會拋出異常,通常情況下不會使用這個方法,使用這個方法是就以為著狀態可能會丟失,最好的解決辦法在activity
狀態保存之前在應用中commit
,除非狀態丟失不可避免,否則就不要用commitAllowingStateLoss()
希望這幾條建議可以幫到你!