? ? ? 最近在bugly看到兩個(gè)關(guān)于java.lang.IllegalStateException的異常,一個(gè)是java.lang.IllegalStateException: Activity has been destroyed。如果不懂的請(qǐng)參考java.lang.IllegalStateException: Activity has b... - 簡書,還有一個(gè)就是本文講的一個(gè)異常情況。不過出現(xiàn)本文這種異常還有一種情況。按照我以往的風(fēng)格直接貼上bug詳情吧。


? ? ?一種是調(diào)用DialogFragment的dismiss()方法產(chǎn)生的異常,一種是點(diǎn)擊back鍵產(chǎn)生的異常 。項(xiàng)目中采用DialogFragment來實(shí)現(xiàn)加載框的效果,至于為啥要用DialogFragment如果不懂請(qǐng)參考DialogFragment的整理,看bugly上面的詳情我們可以知道這個(gè)bug是在調(diào)用DialogFragment的dismiss方法時(shí)產(chǎn)生的或者是點(diǎn)擊back鍵產(chǎn)生的。這兩種情況產(chǎn)生的原因是一致的,下面我重點(diǎn)講解一下本文異常的第一種情況。不過說到這里我們首先要弄清一個(gè)知識(shí)點(diǎn),onSaveInstanceState這個(gè)方法啥時(shí)候回調(diào)用呢?
? ? ? ??
? ? ? 概括的講,onSaveInstanceState 這個(gè)方法會(huì)在activity 將要被kill之前被調(diào)用以保存每個(gè)實(shí)例的狀態(tài),以保證在將來的某個(gè)時(shí)刻回來時(shí)可以恢復(fù)到原來的狀態(tài),但和activity 的生命周期方法onStop 和 onPause 不一樣,與兩者并沒有絕對(duì)的先后調(diào)用順序,或者說并非所有場景都會(huì)調(diào)用onSaveInstanceState 方法。那么onSaveInstanceState 方法何時(shí)會(huì)被調(diào)用呢,或者這么問,什么時(shí)候activity 會(huì)被系統(tǒng)kill 掉呢?有以下幾種比較常見的場景:?
(1)用戶主動(dòng)按下home 鍵,系統(tǒng)不能確認(rèn)activity 是否會(huì)被銷毀,實(shí)際上此刻系統(tǒng)也無法預(yù)測將來的場景,比如說內(nèi)存占用,應(yīng)用運(yùn)行情況等,所以系統(tǒng)會(huì)調(diào)用onSaveInstanceState保存activity狀態(tài) ;
(2)activity位于前臺(tái),按下電源鍵,直接鎖屏;?
(3)橫豎屏切換;?
(4)activity B啟動(dòng)后位于activity A之前,在某個(gè)時(shí)刻activity A因?yàn)橄到y(tǒng)回收資源的問題要被kill掉,A通過onSaveInstanceState保存狀態(tài)。
? ? ?接下來我們來分析一下為啥調(diào)用dismiss()產(chǎn)生bug的原因了,看到這里一定會(huì)有人說看源碼不就得了??!沒錯(cuò)你說的太對(duì)了,那么我們就從dismis()的源碼里面分析了。首先dismiss回調(diào)用dismissInternal(false)

繼續(xù)看源碼:

有沒有覺得很熟悉啊!我們繼續(xù)看commit()源碼,

有人看到這里覺得眼花了,不過你要相信自己的眼睛.沒錯(cuò)又看到了熟悉的代碼了吧!commitInternal(false)


因?yàn)閍llowStateLoss為false因此回調(diào)用checkStateLoss(),至此我們可以很清楚的知道了bugly上面介紹的報(bào)錯(cuò)的內(nèi)容了。

?那我們就來查看一下哪個(gè)地方會(huì)將mStateSaved置為true,經(jīng)過查找我們發(fā)現(xiàn)saveAllState()方法里面

接下來我們就來查找一下saveAllState()方法被調(diào)用的地方。我們知道activity生命周期有一個(gè)方法是onSaveInstanceState(Bundle outState)

繼續(xù)看源碼



? ? ? 至此我們終于可以知道了報(bào)錯(cuò)的原因所在了,我們的activity在某種場景下處于被kill 掉的邊緣,系統(tǒng)就調(diào)用了onSaveInstanceState 方法,這個(gè)方法里面會(huì)調(diào)用 FragmentManager saveAllState 方法,將DialogFragment 的狀態(tài)保存,如果此時(shí)調(diào)用DialogFrament的dismiss()方法就會(huì)報(bào)這個(gè)異常了。因此我們可以調(diào)用DialogFragment的dismissAllowingStateLoss()方法來替換掉dismiss(),說到這里也許有人會(huì)問dismissAllowingStateLoss最終也會(huì)走enqueueAction方法,allowStateLoss為true那么可能會(huì)報(bào)?"Activity has been destroyed"這個(gè)異常了,你這個(gè)問題問的非常好,不過如果你看了我上一篇的文章你也許就知道你想要的答案了。

? ? ?第二種情況是因?yàn)閍ctivity調(diào)用了onSaveInstanceState 方法,這個(gè)方法里面會(huì)調(diào)用 FragmentManager saveAllState 方法,將fragment 的狀態(tài)保存,在狀態(tài)保存后用戶又主動(dòng)調(diào)了 onBackPressed ,而這個(gè)方法的超類super.onBackPressed 方法會(huì)判斷FragmentManager 是否保存了狀態(tài),如果已經(jīng)保存就會(huì)拋出IllegalStateException 的異常 。源碼如下:


? ?又看到了熟悉的方法checkStateLoss();然后你就知道了問題的所在了??針對(duì)onbackpress 導(dǎo)致的異常,也有幾種解決手段?
(1)在api11 以上 不調(diào)用super 的onSaveInstanceState 方法
protectedvoidonSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
這樣做的后果會(huì)導(dǎo)致activity 和 fragment 的所有狀態(tài),在activity 被系統(tǒng)殺死掉后無法保存,所以如果有保存狀態(tài)的需要,這個(gè)方法是不適用的。
(2)重寫 onBackPressed 方法,不調(diào)用super.onBackPressed ,直接調(diào)用finish 。原因很簡單,super.onBackPressed 里面會(huì)調(diào)用FragmentManager popBackStackImmediate() 方法,如果直接掉finish 就不會(huì)觸發(fā)異常,但這種情況只建議在沒有使用 Fragments api時(shí)調(diào)用。
(3)通過反射手段在onSaveInstanceState 方法里調(diào)用 FragmentManagerImpl noteStateNotSaved方法將 mStateSaved 變量置為false ,這樣既不會(huì)導(dǎo)致activity狀態(tài)丟失,也能確保退出時(shí)不會(huì)拋出異常,算是比較優(yōu)雅的處理途徑。
至此:本篇文章結(jié)束,謝謝!
