前言
简述一下故事背景:
这篇文章是基于我们自己项目的方案完善的,,首先市面的技术方案都一样,所以,前半部分的基础知识不全是我码的字,有参考部分别的优秀文章
废话
release版app崩溃了怎么整,直接闪退,闪退多了,用户不保啊。
所以,开发时候多测试,调试,优化。。上线了,就要配套有release的异常收集。大问题赶紧发热更新,小问题,也不至于闪退
正文
异常分两类,可检测的,编译时候as就给提示了。不可检测的,也就是要等到运行时,才会异常:

unCheckedException(非检查异常):Error和RuntimeException以及他们各自的子类,都是非检查异常。换句话说,当我们编译程序的时候,编译器并不会提示我们这些异常。要么我们在编程的时候,对于可能抛出异常的代码加上try…catch,要么就等着运行的时候崩溃就好了。
checkedException(检查异常):除了UncheckedException之外,其他的都是checkedExcption。对于这种异常,我们的代码通常都无法进行编译,因为as都会提示我们出错了。这个时候要强制加上try…catch,或者将异常throw。
原理
方案:基于UncaughtExceptionHandler自定义CrashHandler
三方:bugly,友盟统计。也是基于以上方案来的
/**
* Interface for handlers invoked when a <tt>Thread</tt> abruptly
* terminates due to an uncaught exception.
* 处理接口,当一个线程由于未捕获的异常突然停止的时候调用。
*
* <p>When a thread is about to terminate due to an uncaught exception
* the Java Virtual Machine will query the thread for its
* <tt>UncaughtExceptionHandler</tt> using
* {@link #getUncaughtExceptionHandler} and will invoke the handler's
* <tt>uncaughtException</tt> method, passing the thread and the
* exception as arguments.
* 当一个线程由于一个未捕获的异常即将崩溃的时候,Java虚拟机将会通过【getUncaughtExceptionHandler()】方法,来
* 查询这个线程的【UncaughtExceptionHandler】,并且会调用他的【uncaughtException()】方法,并且把当前线程
* 和异常作为参数传进去。
*
* If a thread has not had its <tt>UncaughtExceptionHandler</tt>
* explicitly set, then its <tt>ThreadGroup</tt> object acts as its
* <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object
* has no
* special requirements for dealing with the exception, it can forward
* the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
* default uncaught exception handler}.
*如果一个线程没有设置他的【UncaughtExceptionHandler】,那么他的ThreadGroup对象就会作为他的
*【UncaughtExceptionHandler】。如果【ThreadGroup】没有特殊的处理异常的需求,那么就会转调
*【getDefaultUncaughtExceptionHandler】这个默认的处理异常的handler。
*(线程组的东西我们先不管,我们只需要知道,如果Thread没有设置【UncaughtExceptionHandler】的话,那么
*最终会调用【getDefaultUncaughtExceptionHandler】获取默认的【UncaughtExceptionHandler】来处理异常)
*
* @see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* 当传过来的【Thread】因为传过来的未捕获的异常而停止时候调用这个方法。
* 所有被这个方法抛出的异常,都将会被java虚拟机忽略。
*/
void uncaughtException(Thread t, Throwable e);
}
如果给一个线程设置了UncaughtExceptionHandler 这个接口:
1、这个线程中,所有未处理或者说未捕获的异常都将会由这个接口处理,也就说被这个接口给try…catch了。
2、在这个线程中抛出异常时,java虚拟机将会忽略,也就是说,java虚拟机不会让程序崩溃了。
3、如果没有设置,那么最终会调用getDefaultUncaughtExceptionHandler 获取默认的UncaughtExceptionHandler 来处理异常。
我们都知道我们的android程序是跑在UI线程中的,而且我们会在程序中创建各种子线程。为了统一,如果我们给每个线程都通过setUncaughtExceptionHandler() 这个方法来设置UncaughtExceptionHandler 的话,未免太不优雅了。在上面官方代码的注释中有一句,就是如果线程没有设置UncaughtExceptionHandler ,那么会通过getDefaultUncaughtExceptionHandler 来获取默认的UncaughtExceptionHandler 来处理异常。
这样的话,我们只需要在我们应用程序打开的时候,设置一个默认的UncaughtExceptionHandler ,就可以统一处理我们应用程序中所有的异常了!
自定义CrashHandler
https://blog.csdn.net/xy4_android/article/details/80846610
感受一下自定义crashhandler啥样的,,代码太多,就不贴我们项目的代码了
注意点
如上,所有未捕获的异常都可以回调到自定义的handler中。工程里可能用了三方统计,如果自己也实现一套方案。Thread.setDefaultUncaughtExceptionHandler(handler)方法被多次调用,会以最后一次调用时传递的handler为准,所以三方统计的可能失效。
下面先讲三方方案,然后讲我们的方案
三方方案
Cockroach
https://github.com/android-notes/Cockroach/blob/master/原理分析.md
我们项目是基于这个的,内部也是Thread.setDefaultUncaughtExceptionHandler
但是注意:UncaughtExceptionHandler 拦截到的子线程的异常,并不会闪退,对用户是无感的。但是如果是ui线程的异常呢?还是会闪退,重启。
Cockroach很巧妙的拦截了主线程的异常:
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (t == Looper.getMainLooper().getThread()) {
//mainlooper,就活在主线程中
safeMode();
}
}
});
private static void safeMode() {
...
while (true) {
//死循环,保证ui线程一直阻塞到这
try {
//不断的读取queue中的Message并执行
//这样就可以保证以后主线程的所有异常都会从我们手动调用的Looper.loop()处抛出,一旦抛出就会被try{}catch捕获,这样主线程就不会crash了
Looper.loop();
} catch (Throwable e) {
isChoreographerException(e);
if (sExceptionHandler != null) {
sExceptionHandler.bandageExceptionHappened(e);
}
}
}
}
来吧,看张图

这就是Cockroach的心态,其他的自行去看看吧,总之所有的异常都捕获了,具体什么情况下重启app,你决定。比如Cockroach的onMayBeBlackScreen回调中,可以重启app
bugly
这里看一下腾讯bugly的实现就知道了
//路径,bugly的jar包 - com.tencent.bugly - crashreport - crash - e
//bugly混淆处理了,异常处理类就是e
public class e implements UncaughtExceptionHandler {
public synchronized void a() {
if (this.j >= 10) {
} else {
Thread.setDefaultUncaughtExceptionHandler(this);
}
}
}
只看关键就行了,一是基于UncaughtExceptionHandler的,二是内部也设置了Thread.setDefaultUncaughtExceptionHandler(this);
所以,集成了三方,又自己实现异常收集的话,是要注意这个问题的
我们项目里同时有自己实现的异常处理,也用了bugly,神奇,怎么处理的,我觉得是瞎猫撞到死耗子了:::详细看下bugly咋处理的
...
protected UncaughtExceptionHandler e;
protected UncaughtExceptionHandler f;
...
public synchronized void a() {
if (this.j >= 10) {
an.a("java crash handler over %d, no need set.", new Object[]{10});
} else {
//下面两行是重点
//下面两行是重点
//下面两行是重点
// 获取先前设置的 ExceptionHandler
// 不要把 ExceptionHandler 的初始化放在 Bugly 初始化后面,就是别的异常处理设置的handler不要在bugly之后就对了。。
UncaughtExceptionHandler var1 = Thread.getDefaultUncaughtExceptionHandler();
if (var1 != null) {
...
if ("com.android.internal.os.RuntimeInit$UncaughtHandler".equals(var1.getClass().getName())) {
// 这里是安卓系统的 ExceptionHandler
// 把原先的暂存起来,改为走自己的
// f 存系统的 ExceptionHandler
// e 存java(也就是app)的 ExceptionHandler
an.a("backup system java handler: %s", new Object[]{var1.toString()});
this.f = var1;
this.e = var1;
} else {
// 这里是是java层面手动设置的 ExceptionHandler
//重点是这,我们自己实现了一套异常收集,,
//这是重点
//这是重点
//这是重点
// 把原先的暂存起来,改为走自己的
an.a("backup java handler: %s", new Object[]{var1.toString()});
this.e = var1;
}
}
//这里表示拦截UncaughtException
Thread.setDefaultUncaughtExceptionHandler(this);
}
}
//UncaughtExceptionHandler的接口方法,异常回调
public void uncaughtException(Thread thread, Throwable ex) {
synchronized(i) {
this.b(thread, ex, true, (String)null, (byte[])null);
}
}
//这里是处理异常的逻辑
public void b(Thread var1, Throwable var2, boolean var3, String var4, byte[] var5) {
...
try {
} catch (Throwable var11) {
} finally {
if (var3) {
if (this.e != null && this.a(this.e)) {
//这是重点
//这是重点
//这是重点
//最后回到原处理流程上去,,
an.e("sys default last handle start!", new Object[0]);
this.e.uncaughtException(var1, var2);
an.e("sys default last handle end!", new Object[0]);
} else if (this.f != null) {
an.e("system handle start!", new Object[0]);
this.f.uncaughtException(var1, var2);
an.e("system handle end!", new Object[0]);
} else {
an.e("crashreport last handle start!", new Object[0]);
this.a(var1, var2);
an.e("crashreport last handle end!", new Object[0]);
}
}
}
}
上面bugly的处理可以看到,如果在bugly之前设置的有Thread.setDefaultUncaughtExceptionHandler,那么bugly会临时保存起来,等到自己处理完,再接着让别的handler处理。。
如果,我们自己的异常处理类在bugly之后初始化的,那,就没bulgy那些故事了
我们的方案
我们用bugly是配合热更新的,实际没咋用,,我们有自己的方案,基于Cockroach实现的,如果不影响用户使用的,都不崩溃,除非页面新建时候渲染失败了,用户用不了,这会触发toast然后重启应用
我们收集的数据,上传到服务器,结合kibana平台图表化异常数据,便于分析
所以,我们基于Cockroach实现的有CrashHandler,用的也有bugly。。
重点就是自己的CrashHandler要放到bugly初始化之前
关于重启app
private void reloadApp() {
try {
Thread.sleep(5000);
Intent intent = new Intent();
intent.setClass(context, LoadActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent restartIntent = PendingIntent.getActivity(context, 9527, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 100, restartIntent);
} catch (Exception e) {
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
网友评论