App的崩溃率,是性能的一个重要的衡量指标。做过客户端开发的朋友,肯定与线上各种各样的崩溃问题打过交道。我们有没有什么办法能提高程序的稳定性、降低崩溃率,甚至做到永不崩溃呢?对于Java崩溃,答案是肯定的。
根据上一篇文章Java和Android崩溃捕获机制,我们知道了Android崩溃捕获的机制。简单总结就是:所有线程的崩溃,最后都会回调到UncaughtExceptionHandler
,调用uncaughtException
方法进行处理。
Android应用程序在发生未捕获崩溃时,会自动退出进程的原因,是因为Android系统在启动的时候,注册了一个KillerAppExceptionHandler
的全局 defaultExceptionHandler
。所有线程的崩溃都会被这个Handler
处理。而这个Handler
的处理逻辑非常简单,就是先打印日志,然后退出进程。所以,在Android系统上运行的App,发生未捕获异常的时候,会自动退出进程。
这篇文章,我们来看看如何实现程序的永不崩溃。
基本原理
基本原理其实很简单,我们可以通过设置自己的defaultExceptionHandler
,来替代系统默认的KillerAppExceptionHandler
。然后在我们的Handler里面,让进程不崩溃,只做一些上报的工作,就可以实现App永不崩溃。
但是,对于主线程,我们不能直接这样处理。因为主线程是有个Looper
,如果程序发生未捕获异常,主线程会被退出,这个时候Looper
也就无法继续执行了,会造成ANR,所以主线程需要单独做处理,让Lopper
一直不退出。
下面我们会对子线程和主线程两种情况分别进行讨论。
子线程
子线程的情况比较简单,当出现未捕获异常时,直接让线程退出即可。
sUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
// 默认的异常拦截处理器,主线程的异常会用try-catch,不会抛出,所以下面的代码拦截到的都是子线程的异常
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if(catchException){
// 可以在这里做一些上报
}else{
// 调用原始的uncaughtExceptionHandler进行处理
sUncaughtExceptionHandler.uncaughtException();
}
}
});
主线程
由于主线程需要一直运行,所以不能直接用抛出异常,uncaughtExceptionHandler
拦截处理的方式,必须让主线程的循环继续执行下去。
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
while (true) {
try {
//主线程的异常会从这里抛出
Looper.loop();
} catch (Throwable e) {
// 可以在这里做一些上报
}
}
}
});
原理相当于我们在Looper的死循环外面又套了一层死循环,这样当内存的死循环因为抛出异常退出的时候,外层的死循环还能继续执行。
大致的运行流程:
- 往主线程的
MessageQueue
中post一个Runnable
,当主线程执行到该Runnable
时,会进入我们的while死循环。 - 在这个循环里面,调用了
Looper.loop()
方法,使得主线程可以循环起来,读queue
中的Message
并执行,也就是主线程不会被阻塞。 - 如果主线程在
Message
执行中抛出异常,因为我们加了try-catch
捕获,所以不会往外抛出异常,而会直接走catch的逻辑。 - 走完catch之后,又再次进入while循环中。
可能引起的ANR问题
如果在主线程抛出异常的时候,拦截异常,但是又不启动主线程的Looper
,就会造成ANR。
比如如下代码,就可能造成ANR的问题:
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread t, Throwable e) {
return;
}
});
所以在使用的UncaughtExceptionHandler
的时候,一定要记得重启主线程的Looper
,否则即使不崩溃,程序也无法继续运行。
几个问题
主线程的方案,相当于在所有的方法执行外层都加了try-catch,会不会影响性能?
答:性能损耗可以忽略,从反编译出的指令发现,加了try-catch块的代码跟没有加的代码运行时的指令是完全一直的。如果程序运行过程中,不产生异常的话,try-catch几乎不会对运行产生任何影响。只有在发生异常的时候,JVM会追溯宜昌站,这部分耗时就比较高了。崩溃后,之后的业务逻辑还能继续执行吗?
答:不可以,子线程如果遇到崩溃就会直接退出,主线程遇到崩溃,当前的Message的执行也会结束。这个方案只能保证不崩溃,无法保证不会有业务逻辑的问题,所以可以在使用的时候增加一些判断条件,比如,遇到特定的已知的崩溃才进行捕获。