遇到一個(gè)很奇特的問題,當(dāng)用戶設(shè)置了PIN碼,在鎖屏界面正常解鎖PIN碼后,進(jìn)入Launcher時(shí)顯示com.Android.phone 已停止運(yùn)行。一開始猜想會(huì)不會(huì)是解鎖PIN碼的時(shí)候處理導(dǎo)致了Phone進(jìn)程報(bào)錯(cuò),通過log分析找到了問題的大概原因:
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: java.lang.IllegalArgumentException: View not attached to window manager
AndroidRuntime: at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:385)
AndroidRuntime: at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:287)
AndroidRuntime: at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:79)
AndroidRuntime: at android.app.Dialog.dismissDialog(Dialog.java:323)
AndroidRuntime: at android.app.Dialog.dismiss(Dialog.java:306)
AndroidRuntime: at com.android.stk.StkDialogActivity$4.onClick(StkDialogActivity.java:188)
AndroidRuntime: at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:169)
AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
AndroidRuntime: at android.os.Looper.loop(Looper.java:153)
AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:5299)
AndroidRuntime: at java.lang.reflect.Method.invokeNative(Native Method)
AndroidRuntime: at java.lang.reflect.Method.invoke(Method.java:511)
AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
AndroidRuntime: at dalvik.system.NativeStart.main(Native Method)
通過分析PIN碼的解鎖流程知道,并不是PIN碼解釋時(shí)的dialog導(dǎo)致的問題。PIN碼解鎖流程另用文檔說明。
仔細(xì)查看log后發(fā)現(xiàn)有:
AndroidRuntime: at com.android.stk.StkDialogActivity$4.onClick(StkDialogActivity.java:188)
這是開機(jī)識(shí)別到SIM卡之后彈出的STK對(duì)話框。那為什么會(huì)報(bào)com.android.phone已停止運(yùn)行呢? 經(jīng)過以上分析,我們可以大致猜測(cè)是因?yàn)镾TK引起的問題,既然是STK的問題那為什么會(huì)報(bào)phone的錯(cuò)誤呢?如果是phone的錯(cuò)誤那么從log中應(yīng)該可以檢索到phone相關(guān)的問題,為什么沒有呢?那么我們接下來一一解答這些問題。
(1). 為什么報(bào)錯(cuò)com.android.phone已停止運(yùn)行?
通過查看STK的源碼(單卡MTK的代碼在/mediatec/packages/app/stk1),在其AndroidManifest.xml可以發(fā)現(xiàn):
android:sharedUserId="android.uid.phone"
也就是STK和Phon在一個(gè)進(jìn)程中,因此在報(bào)錯(cuò)的表現(xiàn)上來講就會(huì)是com.andorid.phone。
(2). 為什么會(huì)報(bào)View not attached to window manager錯(cuò)誤?
這個(gè)錯(cuò)誤的意思是說我們所操作的View沒有被納入window manager的管理。
我們知道所有的窗口創(chuàng)建和管理都是依附于window manager的,因此Dialog的創(chuàng)建也不例外。Dialog的創(chuàng)建流程通過查看源碼可以知道,在Dialog的構(gòu)造函數(shù)中,創(chuàng)建了一個(gè)Window對(duì)象,但我們知道Window對(duì)象并不是用于顯示的,真正用于顯示的是View對(duì)象。因此通過Dialog的show方法構(gòu)造了一個(gè)mDecor的View對(duì)象,并最終通過WindowManager的addView()方法顯示Dialog。
通過查看log信息我們可以看到com.android.stk.StkDialogActivity$4.onClick(StkDialogActivity.java:188)
查看對(duì)應(yīng)的StkDialogActivity代碼后發(fā)現(xiàn),在188行處代碼為dialog.dismiss();在網(wǎng)絡(luò)上搜索后發(fā)現(xiàn),多數(shù)情況下出現(xiàn)這種錯(cuò)誤,都是在dismiss Dialog時(shí),發(fā)現(xiàn)創(chuàng)建該Dialog的Activity存在而導(dǎo)致的。比如在界面上顯示一個(gè)Dialog,當(dāng)任務(wù)處理結(jié)束后再Dismiss Dialog。如果在Dialog顯示期間,該Activity因?yàn)槟撤N原因被殺掉且又重新啟動(dòng)了,那么當(dāng)任務(wù)結(jié)束時(shí),Dismiss Dialog的時(shí)候WindowManager檢查,就會(huì)發(fā)現(xiàn)該Dialog所屬的Activity已經(jīng)不存在了(重新啟動(dòng)了一次,是一個(gè)新的Activity),所以會(huì)報(bào)IllegalArgumentException: View not attached to window manager.
通過以上分析我們可以知道在STK Dialog在執(zhí)行dismiss方法時(shí),發(fā)現(xiàn)啟動(dòng)它的Activity已經(jīng)不見了,被殺掉了(現(xiàn)在這個(gè)是重新啟動(dòng)的),所以才報(bào)錯(cuò)出現(xiàn)異常。
(3). 為什么STKDialogActivity會(huì)被"殺掉"?
通過跟蹤查看異常log我們可以看到StkDialogActivity的生命周期打印log如下:
onCreate
onResume - mbSendResp[false], sim id: 0
... ...省略部分
onSaveInstanceState
onPause, sim id: 0
... ...省略部分
onDestroy-
onCreate
... ...省略部分
onRestoreInstanceState - [com.android.internal.telephony.cat.TextMessage@41fe7d80]
onResume - mbSendResp[false], sim id: 0
到這里后就出現(xiàn)了異常錯(cuò)誤……
通過log我們可以很清楚的看到該Activity啟動(dòng)了兩次。在log中我們也看到了onSaveInstanceState方法。
對(duì)于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)
也就說當(dāng)某個(gè)activity變得“容易”被系統(tǒng)銷毀時(shí),該activity的onSaveInstanceState就會(huì)被執(zhí)行,除非該activity是被用戶主動(dòng)銷毀的,例如當(dāng)用戶按BACK鍵的時(shí)候。
注意這里的容易二字,當(dāng)前Activity并沒有被銷毀,只是系統(tǒng)覺得它有可能會(huì)被銷毀因此會(huì)執(zhí)行該方法。在該方法中我們可以保存Activity中的各種數(shù)據(jù)信息,如果該Activity真的被殺掉而又重新啟動(dòng)后,可以使用onRestoreInstanceState方法在重新啟動(dòng)該Activity時(shí),還原我們之前保存的數(shù)據(jù)信息。onSaveInstanceState方法的調(diào)用遵循一個(gè)重要原則,即當(dāng)系統(tǒng)“未經(jīng)你許可”銷毀了你的Activity時(shí),onSaveInstanceState就會(huì)被系統(tǒng)調(diào)用,這是系統(tǒng)的責(zé)任,因?yàn)樗仨氁峁┮粋€(gè)機(jī)會(huì)讓你保存你的數(shù)據(jù)(當(dāng)然如果你自己不保存,那就沒法恢復(fù)了)。
因此通過以上分析我們可以看到STKDialogActivity的確被殺掉后再次啟動(dòng)了,但為什么會(huì)被殺掉,通過log并沒有找到答案。
(4). 在STKDialogActivity被殺掉時(shí),Dialog存在么?
可能大家會(huì)有疑問,Dialog都沒有看到,就出現(xiàn)錯(cuò)誤了,怎么能確定該Dialog當(dāng)時(shí)一定是顯示的呢?繼續(xù)在log中搜索我們可以發(fā)現(xiàn):
WindowManager: Activity com.android.stk.StkDialogActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{41fea228 V.E..... R.....ID 0,0-1026,433} that was originally added here
WindowManager: android.view.WindowLeaked: Activity com.android.stk.StkDialogActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{41fea228 V.E..... R.....ID 0,0-1026,433} that was originally added here
WindowManager: at android.view.ViewRootImpl.(ViewRootImpl.java:409)
WindowManager: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:218)
WindowManager: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
WindowManager: at android.app.Dialog.show(Dialog.java:281)
WindowManager: at android.app.AlertDialog$Builder.show(AlertDialog.java:951)
WindowManager: at com.android.stk.StkDialogActivity.onCreate(StkDialogActivity.java:192)
WindowManager: at android.app.Activity.performCreate(Activity.java:5122)
WindowManager: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1081)
WindowManager: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2270)
WindowManager: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2358)
WindowManager: at android.app.ActivityThread.access$600(ActivityThread.java:156)
WindowManager: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1340)
WindowManager: at android.os.Handler.dispatchMessage(Handler.java:99)
WindowManager: at android.os.Looper.loop(Looper.java:153)
WindowManager: at android.app.ActivityThread.main(ActivityThread.java:5299)
WindowManager: at java.lang.reflect.Method.invokeNative(Native Method)
WindowManager: at java.lang.reflect.Method.invoke(Method.java:511)
WindowManager: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
WindowManager: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
WindowManager: at dalvik.system.NativeStart.main(Native Method) ```
這里的WindowManager報(bào)錯(cuò)是什么意思呢?這段log是WindowManager拋出的error錯(cuò)誤,當(dāng)我們的Dialog還沒有dismiss時(shí),如果此時(shí)該Activity被銷毀了,那么就會(huì)出現(xiàn)以上錯(cuò)誤,提示窗口泄漏(leaked window)。這里自己也做了一個(gè)實(shí)驗(yàn),寫一個(gè)demo,在Activity的onCreate方法中顯示一個(gè)Dialog,然后直接調(diào)用finish方法。代碼大致如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AlertDialog.Builder info = new Builder(this);
info.setTitle("Dialog").setPositiveButton("OK", null).setMessage("This is a Dialog");;
info.show();
finish();
}
直接將此程序run到模擬器或者真機(jī)上,查看log我們就能看到leaked window的報(bào)錯(cuò)信息。因此這也證明了前面我們的假設(shè),即StkActivity在被銷毀時(shí),其所依附的Dialog是存在的。
(5). 如何解決這個(gè)問題呢?
通過以上分析之后我們知道了問題出現(xiàn)的原因,那么如何解決呢?可以通過以下兩個(gè)方面來解決:
1. 使用Activity自帶的Dialog控制方法
在Activity中需要使用對(duì)話框,可以使用Activity自帶的回調(diào),比如onCreateDialog(),showDialog(),dimissDialog(),removeDialog()等等。畢竟這些都是Activity自帶的方法,所以用起來更方便,也不用顯示創(chuàng)建和操控Dialog對(duì)象,一切都由框架操控,相對(duì)來說比較安全。
2.限制Dialog的生命周期
讓創(chuàng)建的Dialog對(duì)象的存活周期跟Activity的生命周期一致,也就是說Dialog的生命周期被限定在Activity的onCreate()和onDestroy()方法之間。