经过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);
    }
复制代码

一言难尽终于写完了,欢迎你们踊跃指教!(第二节:如何保证应用永不崩溃)