发布于 

Android Java层崩溃监控机制简述(一):Crash收集篇

常见的Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。我们将围绕这两类异常进行。对于很多基于Unity、Cocos平台的游戏,还会有C#、JavaScript、Lua等的异常,这里不做讨论。

Java代码的崩溃机制及实现

Android应用程序的开发是基于Java语言的,所以首先来分析第一类Android崩溃Java Exception。

Exception的分类及捕获

Java的异常可以分为两类:Checked Exception和UnChecked Exception。所有RuntimeException类及其子类的实例被称为Runtime异常,即UnChecked Exception,不是RuntimeException类及其子类的异常实例则被称为Checked Exception。

Checked异常又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。在Java API文档中,方法说明时,都会添加是否throw某个exception,这个exception就是Checked异常。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled exception type xxxxx”。

该类异常捕获的流程是:

执行try块中的代码出现异常,系统会自动生成一个异常对象,并将该异常对象提交给Java运行环境,这个就是异常抛出(throw)阶段;

当Java运行环境收到异常对象时,会寻找最近的能够处理该异常对象的catch块,找到之后把该异常对象交给catch块处理,这个就是异常捕获(catch)阶段。

Checked异常一般是不引起Android App Crash的,注意是“一般”,这里之所以介绍,有两个原因:

形成系统的了解,更好地对比理解UnCheckedException;

对于一些Checked Exception,虽然我们在程序里面已经捕获并处理了,但是如果能同时将该异常收集并发送到后台,将有助于提升App的健壮性。比如修改代码逻辑回避该异常,或者捕获后采用更好的方法去处理该异常。至于应该收集哪些Checked Exception,则取决于App的业务逻辑和开发者的经验了。

UnChecked异常又称为运行时异常,即Runtime-Exception,最常见的莫过于NullPointerException。UnChecked异常发生时,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。当然,你可能会说,那我们把这些异常也try…catch住不就行了。理论上确实是可以的,但有两点会导致这种方案不可行:

无法将所有的代码都加上try…catch,这样对代码的效率和可读性将是毁灭性的;

UnChecked异常通常都是较为严重的异常,或者说已经破坏了运行环境的。比如内存地址,即使我们try…catch住了,也不能明确知道如何处理该异常,才能保证程序接下来的运行是正确的。

没有try…catch住的异常,即Uncaught异常,都会导致应用程序崩溃。那么面对崩溃,我们是否可以做些什么呢?比如程序退出前,弹出个性化对话框,而不是默认的强制关闭对话框,或者弹出一个提示框安慰一下用户,甚至重启应用程序等。

UncaughtExceptionHandler

其实Java提供了一个接口给我们,可以完成这些,这就是UncaughtExceptionHandler,该接口含有一个纯虚函数:public abstract void uncaughtException (Thread thread, Throwableex)。

Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常,然后便会调用uncaughtException函数。如果该handler没有被显式设置,则会调用对应线程组的默认handler。如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置:

1
public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 

实现自定义的handler,只需要继承UncaughtExceptionHandler该接口,并实现uncaughtException方法即可。

1
2
static class MyCrashHandler implements UncaughtExceptionHandler{ @Override public void uncaughtException(Thread thread, final Throwable throwable) { // Deal this exception }
}

在任何线程中,都可以通过setDefaultUncaughtExceptionHandler来设置handler,但在Android应用程序中,全局的Application和Activity、Service都同属于UI主线程,线程名称默认为“main”。所以,在Application中应该为UI主线程添加UncaughtExceptionHandler,这样整个程序中的Activity、Service中出现的UncaughtException事件都可以被处理。

SDK Crash监控兼容

如果多次调用setDefaultUncaughtExceptionHandler设置handler,以最后一次为准。这也就是为什么多个抓崩溃的SDK同时使用,总会有一些SDK工作不正常。某些情况下,用户会既想利用第三方SDK收集崩溃,又想根据崩溃类型做出业务相关的处理。此时有两种方案:

SDK提供了一个接口,允许用户传递自己handler给SDK。当执行uncaughtException的时候,SDK只负责采集崩溃信息,之后便调用用户的handler来处理崩溃了。这儿其实类似于多了一层setDefaultUncaughtExceptionHandler,只是不能和标准库用同样的名称,因为会覆盖。

用户先通过setDefaultUncaughtExceptionHandler设置自己的handler,然后SDK再次通过setDefaultUncaughtExceptionHandler设自handler前,先保存之前的handler,在SDK handler逻辑处理完成之后,再调用之前的handler处理该异常。

1
2
3
4
5
6
7
8
static class MyCrashHandler implements UncaughtExceptionHandler { private UncaughtExceptionHandler originalHandler; private MyCrashHandler(Context context) {
originalHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override public void uncaughtException(Thread thread, final Throwable throwable) { // Deal this exception if (originalHandler != null) {
originalHandler.uncaughtException(thread, throwable);
}
}
}

获取Exception崩溃堆栈

捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。

1
2
3
4
5
6
7
8
9
10
public static String getStackTraceInfo(final Throwable throwable) {
String trace = ""; try {
Writer writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
throwable.printStackTrace(pw);
trace = writer.toString();
pw.close();
} catch (Exception e) { return "";
} return trace;
}

小结

这样监控上报所需要的Crash信息就拿到了,上传部分在之后的文章当中介绍,欢迎交流~


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @tsparrot 创建,使用 Stellar 作为主题。