通过Thread.setDefaultUncaughtExceptionHandler阻止程序崩溃的源码解析

1,954 阅读5分钟

通过Thread.setDefaultUncaughtExceptionHandler阻止程序崩溃的源码解析

国际惯例先上狗图,以防被打

在进行源码分析前我会先普及一些知识作为铺垫,如果了解可以直接略过看正文。

我们常说的主线程也就是MainThread(也是UiThread,这两个只会在特定情况下不相等),下面是谷歌官网的原话。

其实本质就是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来处理吗,请各位看官慢慢看慢慢瞧,不慌,我们一步步来揭晓

1.2 ThreadGroup从哪里来并且为什么可以代替UncaughtExceptionPreHandler来处理异常

1.2.1 ThreadGroup从哪里来 其实我们每个线程在构造的过程中都会初始化一个ThreadGroup,只是我们通常不会手动赋值,而是由系统帮我们初始化完成。

    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。

1.2.2 现在我们再来看看为什么ThreadGroup可以代替UncaughtExceptionHandler

因为ThreadGroup实现了Thread.UncaughtExceptionHandler接口,并默认初始化了两个ThreadGroup(静态),分别是systemThreadGroup和mainThreadGroup(大家看名字就知道他是为主线程服务的),这个两个东西后面有大用大家先记着。

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);
    }

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