Android Crash战斗日记(一、原理篇)
前言
Crash估計是所有Android開發(fā)者的一塊心病,無論是新手小白還是高手大牛,都無法避免遇到Crash。但是Crash是怎么產生的呢,這篇將深入的講解Crash。
一、異常
說到Crash,得先從異常講起,NullPointerException是大家最熟悉的異常之一,下面這個圖片就Bugly上面的一個NullPointerException:
先看看類結構:
發(fā)現(xiàn)其實內部什么都沒有,只是繼承了RuntimeException,看起來好像完全沒有意義,這種形式的設計更多的是將類本身當作一個類型作為判斷。下圖是java標注庫中的類繼承圖。
- Throwable 類是 Java 語言中所有錯誤或異常的超類。只有當對象是此類(或其子類之一)的實例時,才能通過 Java 虛擬機或者 Java throw 語句拋出。類似地,只有此類或其子類之一才可以是 catch 子句中的參數(shù)類型。
- Error 是 Throwable 的子類,用于指示合理的應用程序不應該試圖捕獲的嚴重問題。大多數(shù)這樣的錯誤都是異常條件。雖然 ThreadDeath 錯誤是一個“正規(guī)”的條件,但它也是 Error 的子類,因為大多數(shù)應用程序都不應該試圖捕獲它。
- Exception 類及其子類是 Throwable 的一種形式,它指出了合理的應用程序想要捕獲的條件。
- RuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類。可能在執(zhí)行方法期間拋出但未被捕獲的 RuntimeException 的任何子類都無需在 throws 子句中進行聲明。
二、異常產生過程
異常產生的過程需要從虛擬機講起,虛擬機運行時數(shù)據(jù)區(qū)如下圖所示:
其中虛擬機棧是線程私有的,每個Java方法的調用對應一個棧幀在虛擬機棧中的入棧和出棧。當線程執(zhí)行一個Java方法執(zhí)行時,就會創(chuàng)建一個新的棧幀并壓入到該線程的虛擬機棧的棧頂,Java方法執(zhí)行結束后棧頂?shù)脑摋蜁棾鰲2N毀。
方法出口(返回地址)
當一個方法被執(zhí)行后,有兩種方式退出這個方法。第一種方式是執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令,這時候可能會有返回值傳遞給上層的方法調用者(調用當前方法的方法稱為調用者),是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來決定,這種退出方法的方式稱為正常完成出口(Normal Method Invocation Completion)。
另外一種退出方式是,在方法執(zhí)行過程中遇到了異常,并且這個異常沒有在方法體內得到處理,無論是Java虛擬機內部產生的異常,還是代碼中使用athrow字節(jié)碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種退出方法的方式稱為異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的上層調用者產生任何返回值的。
無論采用何種退出方式,在方法退出之后,都需要返回到方法被調用的位置,程序才能繼續(xù)執(zhí)行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執(zhí)行狀態(tài)。一般來說,方法正常退出時,調用者的PC計數(shù)器的值就可以作為返回地址,棧幀中很可能會保存這個計數(shù)器值。而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。
方法退出的過程實際上等同于把當前棧幀出棧,因此退出時可能執(zhí)行的操作有:恢復上層方法的局部變量表和操作數(shù)棧,把返回值(如果有的話)壓入調用者棧幀的操作數(shù)棧中,調整PC計數(shù)器的值以指向方法調用指令后面的一條指令等。
虛擬機棧Error
Java虛擬機棧有可能出現(xiàn)的error就是StackOverflowError和OutOfMemoryError。當線程請求的棧深度大于Java虛擬機棧允許的深度時,就會拋出StackOverflowError錯誤。比如將一個方法反復遞歸,最終就會出現(xiàn)StackOverflowError。當Java虛擬機棧可以動態(tài)擴展時(大部分的 Java 虛擬機都可動態(tài)擴展,不過 Java 虛擬機規(guī)范中也允許固定長度的虛擬機棧),如果無法申請到足夠的內存來擴展棧,就會拋出OutOfMemoryError錯誤
最終如果異常一直沒有處理,就會通過Thread.dispatchUncaughtException(Throwable e)進行異常分發(fā):
優(yōu)先通過自身的uncaughtExceptionHandler處理異常,如果為null,則通過自身的ThreadGroup處理,ThreadGroup繼承UncaughtExceptionhandler,在類初始化時默認會創(chuàng)建兩個ThreadGroup:main、system,system是main的父ThreadGroup。
最終異常到了system的uncaughtException(Thread t, Throwable e),在defaultUncaughtExceptionHandler中進行處理,Android中默認的是KillApplicationHandler
| /** * Handle application death from an uncaught exception. The framework * catches these for the main threads, so this should only matter for * threads created by applications. Before this method runs, the given * instance of {@link LoggingHandler} should already have logged details * (and if not it is run first). */ private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {private final LoggingHandler mLoggingHandler;/*** Create a new KillApplicationHandler that follows the given LoggingHandler.* If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called* on the created instance without {@code loggingHandler} having been triggered,* {@link LoggingHandler#uncaughtException(Thread, Throwable)* loggingHandler.uncaughtException} will be called first.* * @param loggingHandler the {@link LoggingHandler} expected to have run before* this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}* is being called.*/public KillApplicationHandler(LoggingHandler loggingHandler) {this.mLoggingHandler = Objects.requireNonNull(loggingHandler);}public void uncaughtException(Thread t, Throwable e) {try {ensureLogging(t, e);// Don't re-enter -- avoid infinite loops if crash-reporting crashes.if (mCrashing) return;mCrashing = true;// Try to end profiling. If a profiler is running at this point, and we kill the// process (below), the in-memory buffer will be lost. So try to stop, which will// flush the buffer. (This makes method trace profiling useful to debug crashes.)if (ActivityThread.currentActivityThread() != null) {ActivityThread.currentActivityThread().stopProfiling();}// Bring up crash dialog, wait for it to be dismissedActivityManager.getService().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));} catch (Throwable t2) {if (t2 instanceof DeadObjectException) {// System process is dead; ignore} else {try {Clog_e(TAG, "Error reporting crash", t2);} catch (Throwable t3) {// Even Clog_e() fails! Oh well.}}} finally {// Try everything to make sure this process goes away.Process.killProcess(Process.myPid());System.exit(10);}}復制代碼 |
三、Android主線程異常分析
為什么要單獨分析Android主線程異常呢?大家可能都想過一個問題,如果在uncaughtExceptinHandler中將異常攔截下來,那是不是我們的應用就永遠不會崩潰了。讀者不用再去嘗試了,筆者已經去嘗試過一次了,結果當然是不行的。作為基于事件機制的系統(tǒng),從輪詢任務的過程中跳出后,其實系統(tǒng)就停止了。
以上是Android 26中的ActivityThread.java源碼,看的出來這是一個進程入口,主要的是做Looper的初始化,也是App整個事件機制的開始,其中Looper.loop()就是事件輪詢的開始。
public static void loop() {for(;;) {...Message msg = queue.next(); // might blockmsg.target.dispatchMessage(msg);...} }復制代碼當出現(xiàn)UncaughtException時,會打斷事件輪詢機制,導致App退出。
四、總結
本篇文章總結了Android中Java層中Crash的產生和過程,這將幫助我們去定位問題和解決問題。
總結
以上是生活随笔為你收集整理的Android Crash战斗日记(一、原理篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: orcal 数据库 maven架构 s
- 下一篇: Android8.0适配那点事(二)