android timer后函数继续执行_Android内存异常机制(用户空间)_JE
常見的Android穩定性異常,有內核異常和Android層異常。內核異常也就是常說的“kernel panic”,簡稱KE異常;Android層異常又分為java層crash和Native層crash,簡稱JE、NE異常。此外,Android層異常還有應用ANR和system_Server watchdog異常,這兩種異常是應用或者系統長時間無響應時觸發的。
本文主要介紹android層Java Exception異常的抓取機制和處理方式。下期文章里會再介紹NE異常。
Java Exception
1.簡介??
我們知道java有try-catch的異常捕獲機制,一份健壯的代碼應該在設計和實現時考慮到各種可能遇到的exception異常,捕獲并做相應的處理。
但并非所有的異常都是可被預知的,沒有捕獲到的異常,會被一層層的往上拋,最終由虛擬機的默認異常處理機制來處理。這里會打印出異常的堆棧信息,同時程序將停止運行,也就是我們常見的程序崩潰。
Debug版本上,程序崩潰會彈框提示“xx程序停止運行”。正式用戶版本上,各廠商通常都修改為直接退出應用,移除了彈框提示。
2.異常抓取機制
Java的Thread類提供了一份Java的Thread類中有一個UncaughtExceptionHandler接口,該接口的作用主要是為了當Thread因未捕獲的異常而突然終止時,調用處理程序處理異常。
//UncaughtExceptionHandler接口唯一的回調函數
void uncaughtException(Thread t, Throwable e);
//設置當前線程的異常處理器
Thread.setUncaughtExceptionHandler
//設置所有線程的默認異常處理器
Thread.setDefaultUncaughtExceptionHandler
//設置所有線程的默認異常預處理器
Thread.setUncaughtExceptionPreHandler
Android中也同樣利用了這種機制。虛擬機在遇到線程沒有捕獲的異常時,會調用thread的dispatchUncaughtException分發到當前線程中。
這里的java_lang_Thread_dispatchUncaughtException就是反射的java thread類的dispatchUnCaughtException方法。
這里面會有兩個uncaughtExceptionHandler參與處理,分別是preHandler和Handler,分別執行各自的unCaughtException()方法。
這里,preHandler可以不設置。事實上,Android平臺在N之前只有一個Handler,并沒有設置preHandler。
我們知道,Android系統中,System_server進程和各種應用進程都是從zygote進程孵化而來,zygoteInit的時候會調用Runntime.commonInit(),這里就會設置進程默認的uncaughtExceptionHandler,代碼實現如下:
這里,RunntimeInit初始化了兩個handler,一個是preHandler(LoggingHandler),一個是defaultHangler(KillApplicationHandler)。
兩個類都繼承于Thread.UncaughtExceptionHandler,實現其中的uncaughtException方法。其中:
(1)LoggingHandler:
主要用來打印異常信息,包括是否是SYSTEM_SERVER進程異常,異常進程名、pid信息、異常堆棧等。
我們分析問題常見的“FATAL EXCEPTION:”的打印就是來自于這里。如果是SYSTEM_SERVER進程發生的JE異常,打印的頭部會變成”*** FATAL EXCEPTION IN SYSTEM PROCESS:”.
(2)KillApplicationHandler:
主要用來檢查和確保LoggingHandler的日志打印被調用到,然后通知AMS,殺掉應用進程。
Android N之前,只有一個defaultHandler,日志打印和通知AMS殺進程都是在這個Handler中完成的。
需要注意的是,preHandler的設置并非對外公開api,應用無法使用。但是Thread的setDefaultUncaughtExceptionHandler是公開的api,應用進程可以通過重設它來實現自己捕獲uncaughtException。這種情況下,系統日志中雖然打印了FATAL EXCEPTION,但是應用并沒有死掉。應用可以通過這種方式自己捕獲異常的堆棧,但是通常情況下,捕捉完信息后,建議還是殺掉或者重啟進程,這種如果處理不好的話,容易出現應用假死(無法運行也不會自動退出) 的情況。
在log中打印完日志之后,uncaughtException還會調用AMS的handleApplicationCrash對本次異常進行處理,將拋出的異常throwable通過ApplicationErrorReport類轉換為序列化變量再通過binder傳遞到AMS服務端。
HandleApplicationCrash會先去獲取進程名稱,進程名(processName)獲取方式:
①當遠程Ibinder對象為空,則進程名為“system_server”
②遠程對象不為空時。如果processRecord能查到符合該binder對象的app記錄,則打印processRecord對象中相應的進程名;如果processRecord為空,現場可能已經不完整,進程名復制為“unknown”。
有了crashInfo,又拿到了processRecord信息和進程名,接下來繼續執行crashInner處理方法。HandleApplicationCrashInner主要完成兩件事情,一是調用addErrorToDropBox,將crashInfo的關鍵信息輸出到dropbox文件中,目錄位于data/system/dropbox,根據異常的進程名,一般名為system_server_crash@***.txt,system_app_crash***,data_app_crash***。
寫入的內容包括進程名、pid、uid、時間、flag、包名、前后臺信息、fingerprint版本信息、crashInfo堆棧信息等,廠商定制可能會再加入一些其他的打印。
到這里,日志已經保存完,接下來AppError.crashApplication用來完成最后的收尾工作,主要用來處理應用退出后帶來的狀態切換變化,以及呈現crash彈窗給用戶。該函數中主要的兩段:
① makeAppCrashingLocked。處理應用退出后的邏輯,主要完成的工作:
處理屏幕旋轉以及旋轉動作相關邏輯;
移除屏幕凍結的超時消息;
使能輸入事件分發;
發送configuration改變的消息;
恢復頂部的Activity;
② SHOW_ERROR_UI_MSG。主要用來處理crash之后的彈框提醒,阻塞并等待用戶選擇是否退出,用戶不做選擇超過5min或者手機休眠的話,會自動退出。
至此,JAVA Exception的抓取處理邏輯完成。
3.抓到的日志
JE異常產生后,會先在logcat buffer中產生一系列打印,包含剛才提到的“FATAL EXCEPTION”和“***FATAL EXCEPTION IN SYSTEM PROCESS”, 通常可以以FATAL關鍵字搜索定位。
LOG中的JE異常格式如下:
另外,在data/system/dropbox目錄下,會同步生成一份txt的日志文件,需要root權限才能導出。通常根據crash的進程的不同,前綴可能是“system_app_crash”、“data_app_crash”、“system_server_crash”中的一種。
導出后的內容如下圖:
4.異常堆棧由來
Java exception的產生,主要有兩種原因:
?(1) 編寫的程序代碼內部錯誤產生的異常,如調用對象為空(空指針異常)、數組越界異常、除0異常等。這種通常稱為未檢查的異常,在虛擬機中執行時會集中處理這些異常。
(2) 其他運行中異常,通過throw語句主動拋出的異常。這類異常稱為檢查異常,一般是程序認為自己遇到了嚴重的問題,后續再運行可能會出問題,主動告知方法調用者一些必要的信息。
未檢查的異常是如何讓線程退出循環,執行到uncaughtExceptionHandler的?我們以除0異常來舉例。
經過編譯,這個觸發操作最后會轉化為一條div-int/lint8指令,當art虛擬機需要執行這條語句時,會去先解釋這條語句,通過字符串匹配的方式找到對應的指令代碼,這條語句對應的是DIV_INT_LIT8.
DIV_INT_LIT8會繼續調用DoIntDivide方法。DoIntDivide方法定義如下,這里我們可以看到,當除數divisor為0的時候,虛擬機會通過hrowArithmeticExceptionDivideByZero方法拋出除零異常。
接下來的調用順序:
Art虛擬機對該throw異常,遍歷線程方法棧寄存器和PC指針寄存器, 得到函數方法調用棧等信息,最終用setException方法保存在tlsPtr變量中,等thread.destory調用dispatch uncaughtException的時候,將exception信息傳遞給之前RuntimeInit中注冊的handler去處理。
其他的未檢查的異常也是類似的邏輯。
對于檢查類的異常,這一塊邏輯會簡單很多。通常檢查類的異常都會由某段程序主動調用throw去拋出一個runntimeException,exception可以是原生的類型,例如SecurityException、NullPointerException、IllegalStateException,也可以是自己定義的某個集成于exception的類。
無論是哪種exception還是error,最終都是繼承于Throwable類,throwable類在構造的時候就會調用fillInStackTrace方法。
這個jni方法最終會調用到art虛擬機的Thread:: CreateInternalStackTrace方法中。這就和剛才未檢查異常里面獲取stackTrace用到了同樣的方法。這里會把exception(throwable)的堆棧環境記錄下來。
等需要打印的時候,再通過jni方法來獲取。前面提到的AMS調用handleApplicationCrash這里getStackTrace獲取的就是throwable初始化時記錄下來的函數運行堆棧。
5.分析方法
Java Exception的分析方法相對要簡單很多,java堆棧會保留出錯的調用棧,能精確到代碼指定的行號。如果問題容易復現,可以直接用logcat命令復現并保存日志。如果是已經發生的低概率問題,機器現場還在的話,可以通過導出data/system/dropbox目錄下的日志文件。通常是data_app_crash、system_app_crash、system_server_crash開頭,以txt為后綴。通過分析日志堆棧可以快速定位到出錯的代碼。
例如上圖的異常,堆棧可以明顯看到指定的行號上存在代碼運行空指針異常,并且準確的打印了空指針的變量名稱,開發人員可以檢查相關代碼邏輯,處理即可。
長按關注
內核工匠微信
Linux 內核黑科技 | 技術文章 | 精選教程
總結
以上是生活随笔為你收集整理的android timer后函数继续执行_Android内存异常机制(用户空间)_JE的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言打印菱形_没事了,搞一下C语言打印
- 下一篇: android实现箭头流程列表_Andr