1. 程式人生 > 實用技巧 >03.Android崩潰Crash庫之ExceptionHandler分析

03.Android崩潰Crash庫之ExceptionHandler分析

目錄總結

  • 00.異常處理幾個常用api
  • 01.UncaughtExceptionHandler
  • 02.Java執行緒處理異常分析
  • 03.Android中執行緒處理異常分析
  • 04.為何使用setDefaultUncaughtExceptionHandler

前沿

00.異常處理幾個常用api

  • setUncaughtExceptionHandler
    • public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
      • 設定該執行緒由於未捕獲到異常而突然終止時呼叫的處理程式。
      • 通過明確設定未捕獲到的異常處理程式,執行緒可以完全控制它對未捕獲到的異常作出響應的方式。
      • 如果沒有設定這樣的處理程式,則該執行緒的 ThreadGroup 物件將充當其處理程式。
    • public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
      • 返回該執行緒由於未捕獲到異常而突然終止時呼叫的處理程式。
      • 如果該執行緒尚未明確設定未捕獲到的異常處理程式,則返回該執行緒的 ThreadGroup 物件,除非該執行緒已經終止,在這種情況下,將返回 null。
  • setDefaultUncaughtExceptionHandler
    • public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
      • 設定當執行緒由於未捕獲到異常而突然終止,並且沒有為該執行緒定義其他處理程式時所呼叫的預設處理程式。
      • 未捕獲到的異常處理首先由執行緒控制,然後由執行緒的 ThreadGroup 物件控制,最後由未捕獲到的預設異常處理程式控制。
      • 如果執行緒不設定明確的未捕獲到的異常處理程式,並且該執行緒的執行緒組(包括父執行緒組)未特別指定其 uncaughtException 方法,則將呼叫預設處理程式的 uncaughtException 方法。 -- 通過設定未捕獲到的預設異常處理程式,應用程式可以為那些已經接受系統提供的任何“預設”行為的執行緒改變未捕獲到的異常處理方式(如記錄到某一特定裝置或檔案)。
    • public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
      • 返回執行緒由於未捕獲到異常而突然終止時呼叫的預設處理程式。
      • 如果返回值為 null,則沒有預設處理程式。
  • Thread.UncaughtExceptionHandler
    • public static interface Thread.UncaughtExceptionHandler
      • 所有已知實現類:ThreadGroup
      • 當 Thread 因未捕獲的異常而突然終止時,呼叫處理程式的介面。
      • 當某一執行緒因未捕獲的異常而即將終止時,Java 虛擬機器將使用 Thread.getUncaughtExceptionHandler() 查詢該執行緒以獲得其 UncaughtExceptionHandler 的執行緒,並呼叫處理程式的 uncaughtException 方法,將執行緒和異常作為引數傳遞。
      • 如果某一執行緒沒有明確設定其 UncaughtExceptionHandler,則將它的 ThreadGroup 物件作為其 UncaughtExceptionHandler。
      • 如果 ThreadGroup 物件對處理異常沒有什麼特殊要求,那麼它可以將呼叫轉發給預設的未捕獲異常處理程式。

01.UncaughtExceptionHandler

  • 官方介紹為:
    • Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception.
    • When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandler using getUncaughtExceptionHandler() and will invoke the handler's uncaughtException method, passing the thread and the exception as arguments. If a thread has not had its UncaughtExceptionHandler explicitly set, then its ThreadGroup object acts as its UncaughtExceptionHandler. If the ThreadGroup object has no special requirements for dealing with the exception, it can forward the invocation to the default uncaught exception handler.
  • 翻譯後大概的意思是
    • UncaughtExceptionHandler介面用於處理因為一個未捕獲的異常而導致一個執行緒突然終止問題。
    • 當一個執行緒因為一個未捕獲的異常即將終止時,Java虛擬機器將通過呼叫getUncaughtExceptionHandler() 函式去查詢該執行緒的UncaughtExceptionHandler並呼叫處理器的 uncaughtException方法將執行緒及異常資訊通過引數的形式傳遞進去。如果一個執行緒沒有明確設定一個UncaughtExceptionHandler,那麼ThreadGroup物件將會代替UncaughtExceptionHandler完成該行為。如果ThreadGroup沒有明確指定處理該異常,ThreadGroup將轉發給預設的處理未捕獲的異常的處理器。
  • 異常回調:uncaughtException
    • uncaughtException (Thread t, Throwable e) 是一個抽象方法,當給定的執行緒因為發生了未捕獲的異常而導致終止時將通過該方法將執行緒物件和異常物件傳遞進來。
  • 設定預設未捕獲異常處理器:setDefaultUncaughtExceptionHandler
    • void setDefaultUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh)
    • 設定一個處理者當一個執行緒突然因為一個未捕獲的異常而終止時將自動被呼叫。
    • 未捕獲的異常處理的控制第一個被當前執行緒處理,如果該執行緒沒有捕獲並處理該異常,其將被執行緒的ThreadGroup物件處理,最後被預設的未捕獲異常處理器處理。
    • 通過設定預設的未捕獲異常的處理器,對於那些早已被系統提供了預設的未捕獲異常處理器的執行緒,一個應用可以改變處理未捕獲的異常的方式,例如記錄到指定的裝置或者檔案。
  • handler將會報告執行緒終止和不明原因異常這個情況,如果沒有自定義handler, 執行緒管理組就被預設為報告異常的handler。
    • ThreadHandler 這個類就是實現了UncaughtExceptionHandler這個介面,虛擬碼程式碼如下所示
    public class ThreadHandler implements Thread.UncaughtExceptionHandler {
    
        private Thread.UncaughtExceptionHandler mDefaultHandler;
        private boolean isInit = false;
        /**
         * CrashHandler例項
         */
        private static ThreadHandler INSTANCE;
    
        /**
         * 獲取CrashHandler例項 ,單例模式
         */
        public static ThreadHandler getInstance() {
            if (INSTANCE == null) {
                synchronized (CrashHandler.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new ThreadHandler();
                    }
                }
            }
            return INSTANCE;
        }
    
        /**
         * 當UncaughtException發生時會轉入該函式來處理
         * 該方法來實現對執行時執行緒進行異常處理
         */
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            if (mDefaultHandler != null) {
                //收集完資訊後,交給系統自己處理崩潰
                //uncaughtException (Thread t, Throwable e) 是一個抽象方法
                //當給定的執行緒因為發生了未捕獲的異常而導致終止時將通過該方法將執行緒物件和異常物件傳遞進來。
                mDefaultHandler.uncaughtException(t, e);
            } else {
                //否則自己處理
            }
        }
    
        /**
         * 初始化,註冊Context物件,
         * 獲取系統預設的UncaughtException處理器,
         * 設定該CrashHandler為程式的預設處理器
         * @param ctx
         */
        public void init(Application ctx) {
            if (isInit){
                return;
            }
            //獲取系統預設的UncaughtExceptionHandler
            mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
            //將當前例項設為系統預設的異常處理器
            //設定一個處理者當一個執行緒突然因為一個未捕獲的異常而終止時將自動被呼叫。
            //未捕獲的異常處理的控制第一個被當前執行緒處理,如果該執行緒沒有捕獲並處理該異常,其將被執行緒的ThreadGroup物件處理,最後被預設的未捕獲異常處理器處理。
            Thread.setDefaultUncaughtExceptionHandler(this);
            isInit = true;
        }
    }
    

02.Java執行緒處理異常分析

  • 執行緒出現未捕獲異常後,JVM將呼叫Thread中的dispatchUncaughtException方法把異常傳遞給執行緒的未捕獲異常處理器。
    public final void dispatchUncaughtException(Throwable e) {
        Thread.UncaughtExceptionHandler initialUeh = Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    
    public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
        return uncaughtExceptionPreHandler;
    }
    
  • Thread中存在兩個UncaughtExceptionHandler。一個是靜態的defaultUncaughtExceptionHandler,另一個是非靜態uncaughtExceptionHandler。
    // null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
    
    // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    
    • defaultUncaughtExceptionHandler:設定一個靜態的預設的UncaughtExceptionHandler。來自所有執行緒中的Exception在丟擲並且未捕獲的情況下,都會從此路過。程序fork的時候設定的就是這個靜態的defaultUncaughtExceptionHandler,管轄範圍為整個程序。
    • uncaughtExceptionHandler:為單個執行緒設定一個屬於執行緒自己的uncaughtExceptionHandler,轄範圍比較小。
  • 沒有設定uncaughtExceptionHandler怎麼辦?
    • 如果沒有設定uncaughtExceptionHandler,將使用執行緒所在的執行緒組來處理這個未捕獲異常。
    • 執行緒組ThreadGroup實現了UncaughtExceptionHandler,所以可以用來處理未捕獲異常。ThreadGroup類定義:
    private ThreadGroup group;
    //可以發現ThreadGroup類是整合Thread.UncaughtExceptionHandler介面的
    class ThreadGroup implements Thread.UncaughtExceptionHandler{}
    
  • 然後看一下ThreadGroup中實現uncaughtException(Thread t, Throwable e)方法,程式碼如下
    • 預設情況下,執行緒組處理未捕獲異常的邏輯是,首先將異常訊息通知給父執行緒組,
    • 然後嘗試利用一個預設的defaultUncaughtExceptionHandler來處理異常,
    • 如果沒有預設的異常處理器則將錯誤資訊輸出到System.err。
    • 也就是JVM提供給我們設定每個執行緒的具體的未捕獲異常處理器,也提供了設定預設異常處理器的方法。
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    

03.Android中執行緒處理異常分析

  • 在Android平臺中,應用程序fork出來後會為虛擬機器設定一個未截獲異常處理器, 即在程式執行時,如果有任何一個執行緒丟擲了未被截獲的異常, 那麼該異常最終會拋給未截獲異常處理器處理。
    • 具體可以找到RuntimeInit類,然後在找到KillApplicationHandler類。首先看該類的入口main方法--->commonInit()--->,然後接著往下走,找到setDefaultUncaughtExceptionHandler程式碼如下所示
    • 如果報告崩潰,不要再次進入——避免無限迴圈。如果ActivityThread分析器在此時執行,我們殺死程序,記憶體中的緩衝區將丟失。並且開啟崩潰對話方塊
    • 最後會執行finally中殺死程序的方法幹掉app
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    
    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                if (mCrashing) return;
                mCrashing = true;
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.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);
            }
        }
    }
    
  • UncaughtExceptionHandler存在於Thread中.當異常發生且未捕獲時。異常會透過UncaughtExceptionHandler丟擲。並且該執行緒會消亡。所以在Android中子執行緒死亡是允許的。主執行緒死亡就會導致ANR。
  • 所以其實在fork出app程序的時候,系統已經為app設定了一個異常處理,並且最終崩潰後會直接導致執行該handler的finallly方法最後殺死app直接退出app。如果你要自己處理,你可以自己實現Thread.UncaughtExceptionHandler。

04.為何使用setDefaultUncaughtExceptionHandler

  • Thread.UncaughtExceptionHandler 介面程式碼如下所示
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        void uncaughtException(Thread t, Throwable e);
    }
    
    • UncaughtExceptionHandler 未捕獲異常處理介面,當一個執行緒由於一個未捕獲異常即將崩潰時,JVM 將會通過 getUncaughtExceptionHandler() 方法獲取該執行緒的 UncaughtExceptionHandler,並將該執行緒和異常作為引數傳給 uncaughtException()方法。
    • 如果沒有顯式設定執行緒的 UncaughtExceptionHandler,那麼會將其 ThreadGroup 物件會作為 UncaughtExceptionHandler。
    • 如果其 ThreadGroup 物件沒有特殊的處理異常的需求,那麼就會調 getDefaultUncaughtExceptionHandler() 方法獲取預設的 UncaughtExceptionHandler 來處理異常。
  • 難道要為每一個執行緒建立UncaughtExceptionHandler嗎?
    • 應用程式通常都會建立很多執行緒,如果為每一個執行緒都設定一次 UncaughtExceptionHandler 未免太過麻煩。
    • 既然出現未處理異常後 JVM 最終都會調 getDefaultUncaughtExceptionHandler(),那麼我們可以在應用啟動時設定一個預設的未捕獲異常處理器。即呼叫Thread.setDefaultUncaughtExceptionHandler(handler)
  • setDefaultUncaughtExceptionHandler被呼叫多次如何理解?
    • Thread.setDefaultUncaughtExceptionHandler(handler) 方法如果被多次呼叫的話,會以最後一次傳遞的 handler 為準,所以如果用了第三方的統計模組,可能會出現失靈的情況。對於這種情況,在設定預設 hander 之前,可以先通過 getDefaultUncaughtExceptionHandler() 方法獲取並保留舊的 hander,然後在預設 handler 的uncaughtException 方法中呼叫其他 handler 的 uncaughtException 方法,保證都會收到異常資訊。

專案地址:https://github.com/yangchong211/YCAndroidTool