開發與維運

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

Leave a Reply

Your email address will not be published. Required fields are marked *