天天看點

03.Android崩潰Crash庫之ExceptionHandler分析

目錄總結

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

前沿

  • 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 對象對處理異常沒有什麼特殊要求,那麼它可以将調用轉發給預設的未捕獲異常處理程式。

  • 官方介紹為:
    • 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;
        }
    }           

  • 線程出現未捕獲異常後,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);
            }
        }
    }           

  • 在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。

  • 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 方法,保證都會收到異常資訊。

繼續閱讀