在跑Monkey測試的時候出現了一個比較特別的問題,先來看看Log:
1 // CRASH: com.meizu.media.painter (pid 12491) 2 3 // Short Msg: java.lang.IllegalArgumentException 4 5 // Long Msg: java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{21dd6762 V.ED.... R......D 0,0-538,105} not attached to window manager 6 7 // Build Label: *** 8 9 // Build Changelist: 1443062570 10 11 // Build Time: 1443062722000 12 13 // java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{21dd6762 V.ED.... R......D 0,0-538,105} not attached to window manager 14 15 // at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:416) 16 17 // at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:342) 18 19 // at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:116) 20 21 // at android.app.Dialog.dismissDialog(Dialog.java:354) 22 23 // at android.app.Dialog.dismiss(Dialog.java:337) 24 25 // at com.***.***.***.PainterDrawActivity.closeProgressDialog(PainterDrawActivity.java:60) 26 27 // at com.***.***.painter.PainterDrawActivity.access$000(PainterDrawActivity.java:28) 28 29 // at com.***.***.painter.PainterDrawActivity$1$2.run(PainterDrawActivity.java:49) 30 31 // at android.os.Handler.handleCallback(Handler.java:815) 32 33 // at android.os.Handler.dispatchMessage(Handler.java:104) 34 35 // at android.os.Looper.loop(Looper.java:194) 36 37 // at android.app.ActivityThread.main(ActivityThread.java:5741) 38 39 // at java.lang.reflect.Method.invoke(Native Method) 40 41 // at java.lang.reflect.Method.invoke(Method.java:372) 42 43 // at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980) 44 45 // at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:775) 46 47 //
從上面的Log可以看出是一個非法參數異常的問題。
那為什么會出現Long Msg: java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{21dd6762 V.ED.... R......D 0,0-538,105} not attached to window manager的問題呢?應用本身的崩潰怎么會聯系到
PhoneWindow呢?
(1). 為什么報錯com.android.phone已停止運行?
通過查看網上資料可以知道,在原碼中在其AndroidManifest.xml可以發現:android:sharedUserId="android.uid.phone"也就是和Phone在一個進程中,因此在報錯的表現上來講就會是PhoneWindow。
(2). 為什么會報View not attached to window manager錯誤?
這個錯誤的意思是說我們所操作的View沒有被納入window manager的管理。
我 們知道所有的窗口創建和管理都是依附於window manager的,因此Dialog的創建也不例外。Dialog的創建流程通過查看源碼可以知道,在Dialog的構造函數中,創建了一個Window 對象,但我們知道Window對象並不是用於顯示的,真正用於顯示的是View對象。因此通過Dialog的show方法構造了一個mDecor的 View對象,並最終通過WindowManager的addView()方法顯示Dialog。
通過查看log信息我們可以看到PainterDrawActivity.closeProgressDialog(PainterDrawActivity.java:60)查看對應的closeProgressDialog代碼后發現,在60行處代碼為dialog.dismiss();
在網絡上搜索后發現,多數情況下出現這種錯誤,都是在dismiss Dialog時,發現創建該Dialog的Activity存在而導致的。
比如在界面上顯示一個 Dialog,當任務處理結束后再Dismiss Dialog。如果在Dialog顯示期間,該Activity因為某種原因被殺掉且又重新啟動了,那么當任務結束時,Dismiss Dialog的時候WindowManager檢查,就會發現該Dialog所屬的Activity已經不存在了(重新啟動了一次,是一個新的 Activity),所以會報IllegalArgumentException: View not attached to window manager.
通過以上分析我們可以知道在Dialog在執行dismiss方法時,發現啟動它的Activity已經不見了,被殺掉了(現在這個是重新啟動的),所以才報錯出現異常。
(3). 為什么Activity會被"殺掉"?
由於是Monkey測試出現一次的Log,我們很難復現,只能從網上遇到類似問題的人看看他們的看法。
對於onSaveInstanceState方法,在Android SDK里面有這樣的描述:Android calls onSaveInstanceState() before the activity becomes vulnerable to being destroyed by the system, but does not bother calling it when the instance is actually being destroyed by a user action (such as pressing the BACK key)
也就說當某個activity變得“容易”被系統銷毀時,該activity的onSaveInstanceState就會被執行,除非該activity是被用戶主動銷毀的,例如當用戶按BACK鍵的時候。注意這里的容易二字,當前Activity並沒有被銷毀,只是系統覺得它有可能會被銷毀因此會執行該方法。在該方法中我們可以保存Activity中的各種 數據信息,如果該Activity真的被殺掉而又重新啟動后,可以使用onRestoreInstanceState方法在重新啟動該Activity 時,還原我們之前保存的數據信息。onSaveInstanceState方法的調用遵循一個重要原則,即當系統“未經你許可”銷毀了你的 Activity時,onSaveInstanceState就會被系統調用,這是系統的責任,因為它必須要提供一個機會讓你保存你的數據(當然如果你自 己不保存,那就沒法恢復了)。
因此通過以上分析我們可以看到Activity的確被殺掉后再次啟動了。
(4). 在SActivity被殺掉時,Dialog存在么?
可能大家會有疑問,Dialog都沒有看到,就出現錯誤了,怎么能確定該Dialog當時一定是顯示的呢?
其實Activity在被銷毀時,其所依附的Dialog是存在的。
(5). 如何解決這個問題呢?
通過以上分析之后我們知道了問題出現的原因,那么如何解決呢?可以通過以下兩個方面來解決:
1. 使用Activity自帶的Dialog控制方法
在 Activity中需要使用對話框,可以使用Activity自帶的回調,比如 onCreateDialog(),showDialog(),dimissDialog(),removeDialog()等等。畢竟這些都是 Activity自帶的方法,所以用起來更方便,也不用顯示創建和操控Dialog對象,一切都由框架操控,相對來說比較安全。
2. 限制Dialog的生命周期
讓創建的Dialog對象的存活周期跟Activity的生命周期一致,也就是說Dialog的生命周期被限定在Activity的onCreate()和onDestroy()方法之間。