使用Fragment Transaction時Activity的狀態(tài)丟失

使用FragmentTransactioncommit方法經(jīng)常遇見下面的crash

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)

下面解釋一下為什么會拋出這個exception,然后提出幾條避免這個exception的有效的建議:

為什么會產(chǎn)生這個exception

exception產(chǎn)生的原因是調(diào)用onSaveInstanceState(),Activity的state保存之后,又嘗試去提交一個FragmentTransaction,從而造成Activity狀態(tài)丟失(Activity state loss)的現(xiàn)象,

那么先看一下onSaveInstanceState()這個方法做了什么,在Android系統(tǒng)中,應(yīng)用程序幾乎不能控制自己的命運(yùn),Android系統(tǒng)有權(quán)在任何時候殺死APP來釋放內(nèi)存,并且位于后臺的Activity可能在沒有提示的情況被殺死,為了保證這些不穩(wěn)定的表現(xiàn)對用戶不可見,在Activity遭到破壞之前,Activity可以通過調(diào)用onSaveInstanceState()來保存自己的狀態(tài),之后保存的狀態(tài)恢復(fù),用戶感覺就像無縫的切換了Activity,就像Activity沒有被系統(tǒng)殺掉一樣。

當(dāng)系統(tǒng)調(diào)用onSaveInstanceState()時,會傳給Activity一個Bundle用來保存當(dāng)前Activity的狀態(tài),Activity記錄下它的Dialog,F(xiàn)ragment和View的狀態(tài),當(dāng)該方法返回時,系統(tǒng)將Bundle對象包裝在Binder接口中,并將其A安全的存貯在System Server進(jìn)程中,當(dāng)系統(tǒng)稍后決定重建Activity時,這個Bundle將會被傳回應(yīng)用,來恢復(fù)Activity舊的狀態(tài)。

Exception產(chǎn)生的原因,是因?yàn)樵?code>onSaveInstanceState()調(diào)用時,這個Bundle對象僅僅代表Activity的一個snapshot,這意味著在onSaveInstanceState()之后調(diào)用FragmentTransaction#commit()這個事務(wù)將不會被記住,因?yàn)樗跔顟B(tài)保存的時候并沒有被記錄為Activity狀態(tài)的一部分,從用戶的角度來看,這個事務(wù)看起來像是丟失了,導(dǎo)致UI狀態(tài)丟失,為了保證用戶體驗(yàn),當(dāng)發(fā)生狀態(tài)丟失時,Android會拋出IllegalStateException

什么時候拋出這個Exception

這個異常的拋出時機(jī)在Android 3.0之前和Android 3.0 之后的有一些差別,在Android 3.0前后Activity的生命周期有了很大的變化,在3.0之前,Activity在onPause()之后便認(rèn)為是可以殺死的,因此onSaveInstanceState()onPause()之前立即被調(diào)用,然而在Android3.0之后,Activity只有在onStop()之后才被認(rèn)為是可以殺死的,這意味著onSaveInstanceState()onStop()之前被調(diào)用,而不是onPause() 見下表格:

Android 3.0之前 Android 3.0之前
Activities can be killed before onPause()? NO NO
Activities can be killed before onStop()? YES NO
onSaveInstanceState(Bundle) 在什么函數(shù)前被調(diào)用 onPause() onStop()

由于Android 3.0生命周期發(fā)生了一些變化,Support Library需要根據(jù)不同的版本產(chǎn)生不同的行為

例如:

Android 3.0之前 Android 3.0之前
commit() before onPause() OK OK
commit() between onPause() and onStop() STATE LOSS OK
commit() after onStop() EXCEPTION EXCEPTION

在Android 3.0以及之后的設(shè)備上,每次在onSaveInstanceState()后面調(diào)用commit()都會拋出異常,但是,每次發(fā)生這種情況時都會拋出異常,這對3.0以下的設(shè)備上的限制太多了,為了更好的與舊版本平臺進(jìn)行交互,因此他們做出妥協(xié),在較舊的設(shè)備上必須忍受onPause()onStop()之間可能意外丟失的狀態(tài),見下表

Android 3.0之前 Android 3.0之前
commit() before onPause() OK OK
commit() between onPause() and onStop() STATE LOSS OK
commit() after onStop() EXCEPTION EXCEPTION

如何避免這個Exception

當(dāng)在程序中使用FragmentTransactions時,下面是一些建議

  • 注意在Activity的生命周期方法中提交事務(wù),在第一次調(diào)用onCreate()時或者為了響應(yīng)用戶事件提交事務(wù)不會有什么問題,如果是在onCreate()之外的方法中提交事務(wù)則在FragmentActivity#onResumeFragments()Activity#onPostResume()中執(zhí)行。這兩種方法保證在Activity恢復(fù)到原來的狀態(tài)之后被調(diào)用,從而避免了狀態(tài)一起丟失的可能性。
  • 避免在異步回調(diào)方法中執(zhí)行事務(wù),AsyncTask、Loader等,在異步回調(diào)方法中,不能確定當(dāng)前Activity生命周期的狀態(tài),容易發(fā)生異常,如果應(yīng)用程序需要在異步回調(diào)方法中執(zhí)行事務(wù),那么可以使用commitAllowingStateLoss()
  • 使用commitAllowingStateLoss()是作為最后的兜底方法,調(diào)用commit()commitAllowingStateLoss()之間的唯一區(qū)別是,如果發(fā)生狀態(tài)丟失,后者不會拋出異常。 通常不建議使用這種方法,因?yàn)檫@意味著有可能發(fā)生狀態(tài)損失。除非無法避免狀態(tài)丟失的可能性,否則不應(yīng)使commitAllowingStateLoss()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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