自從Android Honeycomb發佈以來,下面的異常信息和trace已經在StackOverflow提出了不少了:html
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)
這篇博文會解釋這個異常信息爲何會產生,何時會被拋出。而且經過提供幾個建議確保你的App不再會所以而掛掉。java
當Activity狀態(state)已經被保存以後,而你又試圖提交一個Fragment事務,系統就會拋出這個異常,直接致使的現象就是Activity的狀態丟失了。在咱們深刻問題以前,讓咱們先看一下當onSaveInstanceState()
被調用時系統作了什麼。就像我之前博文Binders & Death Recipients裏所說的,Android應用程序很難控制本身的命運。Android有能力在任什麼時候候幹掉進程去釋放內存,而且後臺活動可能毫無預兆地被幹掉。爲了確保用戶有時的意外行爲,系統框架經過在Actvitiy被幹掉以前調用onSaveInstanceState()
方法去保存Activity的狀態。當保存的狀態唄還原後,用戶可以在切換前臺和後臺活動時感覺到無縫切換,而不用考慮活動是否已經被系統幹掉了。android
當系統調用onSaveInstanceState()
方法時,系統傳遞了一個Bundle
對象好讓Activity可以保存它的狀態,之中Activity保存的狀態包括對話框、Fragment和各類視圖等。當這個方法返回後,系統會序列化Bundle
對象經過Binder接口安全的存放在系統進程中。當系統決定從新建立這個Activity後,系統會發送一樣的Bundle
到這個應用,被用來還原Acitivity的老的狀態。安全
那麼爲何會拋出這個異常呢?這個問題的實質就是當Activity調用onSaveInstanceState()
方法後,Bundle
對象保存了這個Activity的「快照」。這就意味着當你在onSaveInstanceState()
被調用以後調用的FragmentTransaction#commit()
,由於Activity已經保存了當前的狀態而且不會記錄這個事務,這個事務的提交就不會被記住。從用戶的角度來看,這個事務會被丟掉,致使UI的狀態丟失。爲了保證用戶體驗,Android不惜一切避免狀態丟失,當它發生的時候,就直接爆出一個IllegalStateException異常。app
若是你之前碰見過這個異常,或許你已經注意到不一樣版本平臺異常拋出的時間略有不一樣。例如,你可能發現老設備更少地拋出這個異常,或者你的應用更可能crash在使用support庫而不是官方系統庫。這就致使了不少人控訴support庫有bug而且不可信。事實上,這些控訴都是不正確的。框架
這些不一致的現象的緣由是由於Honeycomb版本對Activity的生命週期作了重大的修改。在Honeycomb以前,Activity在被暫停以前是不可被殺的,這意味着onSaveInstanceState()
是在onPause()
以前調用的。自從Honeycomb版本,Activity只能在中止之後是可被殺的,意味着onSaveInstanceState()
會在onStop()
以前調用,而不是在onPause()
以前調用。總結以下:異步
Honeycomb以前 | Homeycomb以後 | |
Activity能在onPause() 以前被幹掉 |
NO | NO |
Activity能在onStop() 以前被幹掉 |
YES | NO |
onSaveInstanceState(Bundle) 一般在哪一個方法以前調用? |
onPause() |
onStop() |
由於Activity生命週期的改變,support庫有時須要根據平臺版本的不一樣而改變它的行爲。例如,設備運行在Honeycomb或以上的系統,每次在onSaveInstanceState()
後調用commit()
方法,系統都會拋出異常去警告開發者發生了狀態丟失。然而,每次都拋出一個異常是過於嚴格的在設備運行Honeycomb之前的系統,它們的onSaveInstanceState()
在Activity生命週期中被更早的調而且更容易致使狀態丟失。Android團隊不得不作出了妥協:在了在老系統上擁有更好的交互操做,在onPause()
和onStop()
之間,老設備只能承受狀態丟失。Support庫在不一樣版本上不一樣的行爲可總結爲:async
Honeycomb以前 | Homeycomb以後 | |
commit() 在onPause() 以前 |
OK | OK |
commit() 在onPause() 和onStop() 之間 |
STATE_LOSS | OK |
commit() 在onStop() 以後 |
EXCEPTION | EXCEPTION |
一旦你理解了這個異常的實質狀況,避免這個異常就變的很是簡單了。若是你已經作到了這一步,但願你能理解Support是怎樣工做的,而且爲何在應用中避免狀態丟失是若是的重要。若是你只是在查找快速解決方法,下面是一些關於使用FragmentTransaction的建議,在往後的工做中牢記在心:post
在Activity生命週期方法提交事務要格外當心。絕大多數的應用在onCreate()
方法的時候或者用戶操做的時候就提交了事務,因此不會出現問題。可是,當你在Activity其餘生命週期方法中調用事務,如onActivityResult()
、onStart()
和onResume()
方法等,事情就變的不那麼美好了。例如,你不該該在FragmentActivity#onResume()
方法中提交事務,由於有時候這個方法會在Activity還原狀態以前被調用(詳情見文檔)。若是你的應用真的須要在Activity生命週期非onCreate()
方法中提交事務,請在FragmentActivity#onResumeFragments()
或Activity#onPostResume()
方法中提交。這兩個方法會在Activity還原狀態以後被調用,所以能夠避免狀態丟失(一個例子關於怎麼使用,請看我對[這個問題的的答案](http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult),裏面提供了一些關於在Activity#onActivityResult()
方式中提交FragmentTransaction的想法。)。this
在異步回調方法中避免調用事務。這裏一般包括了像AsyncTask#onPostExceute()
和LoaderManager.LoaderCallback#onLoadFinished()
方法。在這裏調用事務的問題在於當這些方法被調用時他們並不知道當前Activity生命週期狀態。例如,考慮一下的步驟:
一個Activity啓動了一個AsyncTask。
用戶按下了Home鍵,致使這個Activity的onSaveInstanceState()
和onStop()
方法被調用。
這個AsyncTask完成了而且調用了onPostExecute()方法,而沒有意識到這個Activity已經被Stopped。
在onPostExecute()方法中提交了一個FragmentTransaction,而致使拋出了一個異常。
一般,避免這個異常最好的方法就是不要在全部的異步回調方法中提交事務。Google工程師好像也堅決這個信念。根據在Google Group上的關於Adnroid Developer的這篇博文,Android團隊認爲在異步回調方法中提交FragmentTransaction並轉變UI是有損用戶體驗的。若是你應用須要在異步回調方法中調用事務,而且也沒有簡單的方法去確保回調方法不會在onSaveInstanceState()
方法後調用,你只能用最不推薦的方法commitAllowingStateLoss()
,你還要處理可能出現的狀態丟失。(想要了解更多的這部份內容,請參考這個還有這個)
最後再考慮使用commitAllowingStateLoss()
方法。調用commit()
和commitAllowingStateLoss()
惟一的不一樣就是,當狀態丟失發生後,後者不會拋出異常。通常你不想使用這個方法是由於它意味着狀態丟失會有可能發生。固然,更好的解決方法就是讓你應用必定要在Activity保存狀態以前調用commit()
方法,這就是更好的用戶體驗。出發狀態丟失不能被避免,不然commitAllowingStateLoss()
不該該被使用。
但願這些tips可以幫助你解決之前遇到的異常。若是你仍然還有問題,在StackOverflow上提一個問題並在評論中留下連接,我會去看一看的。:)