經過Thread.setDefaultUncaughtExceptionHandler阻止程序崩潰的源碼解析

經過Thread.setDefaultUncaughtExceptionHandler阻止程序崩潰的源碼解析

國際慣例先上狗圖,以防被打bash

在進行源碼分析前我會先普及一些知識做爲鋪墊,若是瞭解能夠直接略過看正文。markdown

咱們常說的主線程也就是MainThread(也是UiThread,這兩個只會在特定狀況下不相等),下面是谷歌官網的原話。app

其實本質就是Thread類的子類,你們能夠經過sdk manger下載源碼而後進入sources目錄下選擇相應的源碼進行查看,我這裏用的是27版本。這裏因爲谷歌屏蔽了MainThread的實現類可是UiThread類和MainThread是同樣的,就直接給你們看下UiThread的繼承關係:

UiThread-->ServiceThread-->HandlerThread-> Thread
複製代碼

因此Thread類中的方法,MainThread也有。源碼分析

一.Thread處理UncaughtException的流程

1.1 既然要處理UncaughtException確定Thread先要分發,

因此在Thread類中咱們輕鬆就找到了dispatchUncaughtException方法。經過註釋咱們能夠清晰的知道,這個方法就是用來處理uncaught exception的分發,不過這個方法只能被runtime和tests調用,咱們沒法操做。憑藉該方法咱們能夠得知在捕獲到未被處理的異常時,各個線程內部會優先檢查該線程是否設置過UncaughtExceptionPreHandler(系統方法咱們沒法調用是用來打印異常日誌,可是後面咱們會講到),若是有設置過則會直接調用該hanlder先打印異常日誌,沒有設置則會直接經過getUncaughtExceptionHandler()獲取group(ThreadGroup)來處理異常。

各位看官確定會疑惑爲何ThreadGroup能夠處理異常並且這個時候不該該返回DefaultUncaughtExceptionHandler來處理嗎,請各位看官慢慢看慢慢瞧,不慌,咱們一步步來揭曉post

1.2 ThreadGroup從哪裏來而且爲何能夠代替UncaughtExceptionPreHandler來處理異常

1.2.1 ThreadGroup從哪裏來 其實咱們每一個線程在構造的過程當中都會初始化一個ThreadGroup,只是咱們一般不會手動賦值,而是由系統幫咱們初始化完成。this

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        Thread parent = currentThread();
        if (g == null) {
            g = parent.getThreadGroup();
        }

        g.addUnstarted();
        this.group = g;

        this.target = target;
        this.priority = parent.getPriority();
        this.daemon = parent.isDaemon();
        setName(name);

        init2(parent);

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        tid = nextThreadID();
    }
複製代碼

經過init方法咱們能夠看出在線程初始化當ThreadGroup爲null的時候系統會默認拿當前線程的ThreadGroup賦值給建立的子線程。因此在主線程上建立的全部線程在沒有單獨設置ThreadGroup的狀況下他們的ThreadGroup都是同一個,就是主線程的ThreadGroup。spa

1.2.2 如今咱們再來看看爲何ThreadGroup能夠代替UncaughtExceptionHandler線程

由於ThreadGroup實現了Thread.UncaughtExceptionHandler接口,並默認初始化了兩個ThreadGroup(靜態),分別是systemThreadGroup和mainThreadGroup(你們看名字就知道他是爲主線程服務的),這個兩個東西后面有大用你們先記着。 3d

1.3 ThreadGroup是如何調用咱們設置的DefaultUncaughtExceptionHandler來實現全局線程異常捕獲處理

在ThreadGroup的uncaughtException方法中,首先會去尋找該ThreadGroup的 的parent,若是當前ThreadGroup有父ThreadGroup的時,使用父ThreadGroup的uncaughtException方法處理異常。若是沒有則會經過Thread獲取咱們設置的DefaultUncaughtExceptionHandler(該對象是Thread類中的靜態變量)。當咱們沒有設置DefaultUncaughtExceptionHandler時該對象默認爲null,不過在應用孵化的過程當中系統會對它進行賦值,也就是KillApplicationHandler(它是用來專門殺死進程的後面會講)。日誌

在前面ThreadGroup類內部默認初始化了systemThreadGroup和mainThreadGroup,老司機們一聽名字就知道他們是爲誰所用。其中mainThreadGroup是以systemThreadGroup爲parent,而systemThreadGroup的parent在初始化的時候設置爲null。

private ThreadGroup() {     // called from C code
        this.name = "system";
        this.maxPriority = Thread.MAX_PRIORITY;
        this.parent = null;
    }
    
    public ThreadGroup(ThreadGroup parent, String name) {
        this(checkParentAccess(parent), parent, name);
    }
    
    private ThreadGroup(Void unused, ThreadGroup parent, String name) {
        this.name = name;
        this.maxPriority = parent.maxPriority;
        this.daemon = parent.daemon;
        this.vmAllowSuspension = parent.vmAllowSuspension;
        this.parent = parent;
        parent.add(this);
    }
複製代碼

那麼關鍵來告終合咱們以前1.2.1所說,因爲全部主線程建立的子線程默認狀況下共用一個ThreadGroup,而這個ThreadGroup就是mainThreadGroup(你們能夠去驗證主線程的ThreadGroup輸出name是不是main).

//在主線程中執行這個方法
Thread.currentThread().getThreadGroup().getName()
複製代碼

因此mainThreadGroup在執行uncaughtException時,而且他的parent是systemThreadGroup也就是null,因此全部線程都會調用Thread.getDefaultUncaughtExceptionHandler()來處理異常。咱們一旦初始化了DefaultUncaughtExceptionHandler他就會在當前應用中全局捕獲全部線程未處理的異常。

這個時候你們是否是會疑惑由於默認狀況下DefaultUncaughtExceptionHandler是爲null的,應用是如何處理異常和殺死應用的呢?

由於應用進程由Zygote進程孵化而來,zygote進程fork自身,開啓一個Linux進程和一個主線程,ZygoteInit類中的zygoteInit方法隨着被調用,該方法中會執行RuntimeInit中的commonInit()方法來設置殺死應用的異常處理器,

應用初始化的過程當中,系統會默認建立KillApplicationHandler設置給DefaultUncaughtExceptionHandler,聽名字就知道KillApplicationHandler就是專門用來殺死進程的。其中Thread.setUncaughtExceptionPreHandler(new LoggingHandler())就是咱們不管怎麼crash都會打印的日誌handler。

下面這個就是ZygoteInit類中的zygoteInit方法,這個方法除了會進行上面所述的調用,還會執行RuntimeInit.applicationInit();在這個方法調用過程當中會經過反射拿到Activity Thread中的main方法,開啓主線程的輪詢。

public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }
複製代碼

一言難盡終於寫完了,歡迎你們踊躍指教!(第二節:如何保證應用永不崩潰)

相關文章
相關標籤/搜索