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