一、背景
无论是Java还是Android项目,往往都会用到多线程。不管是主线程还是子线程,在运行过程中,都有可能出现未捕获异常。未捕获异常中含有详细的异常信息堆栈,可以很方便的去帮助我们排查问题。
默认情况下,异常信息堆栈都会在输出设备显示,同时,Java & Android为我们提供了未捕获异常的处理接口,使得我们可以去自定义异常的处理,甚至可以改变在异常处理流程上的具体走向,如常见的将异常信息写到本地日志文件,甚至上报服务端等。
在未捕获异常的处理机制上,总体上,Android基本沿用了Java的整套流程,同时,针对Android自身的特点,进行了一些特别的处理,使得在表现上与Java默认的流程会有一些差异。
二、未捕获异常处理流程
2.1 引子
我们先可以思考几个问题:
1,Java子线程中出现了未捕获的异常,是否会导致主进程退出?
2,Android子线程中出现了未捕获的异常,是否会导致App闪退?
3,Android项目中,当未作任何处理时,未捕获异常发生时,Logcat中的异常堆栈信息是如何输出的?
4,Android项目中,可能引入了多个质量监控的三方库,为何三方库之间,甚至与主工程之间都没有冲突?
5,Android中因未捕获异常导致闪退时,如何处理,从而可以将异常信息写到本地日志文件甚至上报服务端?
6,Java & Android对未捕获异常的处理流程有何异同?
先来看下第1个问题:
Java子线程中出现了未捕获的异常,是否会导致主进程退出?
可以做一个实验:
package com.corn.javalib;
public class MyClass {
public static void main(String[] args) {
System.out.println("thread name:" + Thread.currentThread().getName() + " begin...");
Thread thread = new Thread(new MyRunnable());
thread.start();
try {
Thread.currentThread().sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread name:" + Thread.currentThread().getName() + " end...");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("thread name:" + Thread.currentThread().getName() + " start run");
errorMethod();
System.out.println("thread name:" + Thread.currentThread().getName() + " end run");
}//需要获取资料的朋友请加Q君样:290194256*
}
public static int errorMethod() {
String name = null;
return name.length();
}
}
执行Java程序,最后输出结果为:
thread name:main begin...
thread name:Thread-0 start run
Exception in thread "Thread-0" java.lang.NullPointerException
at com.corn.javalib.MyClass.errorMethod(MyClass.java:35)
at com.corn.javalib.MyClass$MyRunnable.run(MyClass.java:26)
at java.lang.Thread.run(Thread.java:748)
thread name:main end...
Process finished with exit code 0
我们发现,主线程中新起的子线程在运行时,出现了未捕获异常,但是,main主线程还是可以继续执行下去的,对整个进程而言,最终是Process finished with exit code 0,说明也没有异常终止。
因此,第一个问题的结果是:
Java子线程中出现了未捕获的异常,默认情况下不会导致主进程异常终止。
第2个问题:
Android子线程中出现了未捕获的异常,是否会导致App闪退?
同样的,新建Android工程后,模拟对应的场景,例如点击按钮,启动子线程,发现App直接闪退,AS Logcat中对应有如下日志输出:
2019-11-21 19:10:42.678 26259-26449/com.corn.crash I/System.out: thread name:Thread-2 start run
2019-11-21 19:10:42.679 26259-26449/com.corn.crash E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.corn.crash, PID: 26259
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
at com.corn.crash.MainActivity.errorMethod(MainActivity.java:76)
at com.corn.crash.MainActivity$MyRunnable.run(MainActivity.java:67)
at java.lang.Thread.run(Thread.java:764)
2019-11-21 19:10:42.703 26259-26449/com.corn.crash I/Process: Sending signal. PID: 26259 SIG: 9
从日志信息上看,SIG: 9,意味着App进程被kill掉,日志信息堆栈中给出了具体的异常位置,于是,我们得出如下结论:
默认情况下,Android子线程中出现了未捕获的异常,在是会导致App闪退的,且有异常信息堆栈输出。
我们发现,基于Java基础上的Android,默认情况下,对于子线程中的未捕获异常,在进程是否异常退出方面,却有着相反的结果。
2.2 未捕获异常处理流程
接下来看下第3个问题:
Android项目中,当未作任何处理时,未捕获异常发生时,Logcat中的异常堆栈信息是如何输出的?
当Android项目中出现未捕获异常时,Logcat中默认会自动有异常堆栈信息输出,且信息输出的前缀为: E/AndroidRuntime: FATAL EXCEPTION:。我们很容易猜想到,这应该是系统层直接输出的,搜索framework源码,很快可以找到具体输出日志的位置:
在RuntimeInit.java中,找到了对应的异常日志输出位置,从代码注释上,我们找到了关键的KillApplicationHandler和UncaughtExceptionHandler类,先看下KillApplicationHandler类。
显然,KillApplicationHandler是未捕获异常发生时,默