目前大多数app都是使用三方库(例如友盟)实现崩溃日志收集, 但不一定了解是如何实现的。 今天工作不忙, 刚好有时间思考一下这个问题。
我们知道Android进程在闪退或崩溃时, logcat里会输出一片红色的崩溃日志, 包括Shutting down vm和堆栈信息。 PS: Android基础知识点:app和linux进程是什么关系? 答:每个android进程可以理解为是一个linux进程。
下面说说我理解的做日志采集的套路:
1、 自定义Application类, 一般在这个类里初始化三方库。
<pre>
<application
android:name=".TheApplication"
android:icon="${icon}"
android:label="${str}"
android:largeHeap="true"
tools:replace="android:label"></pre>
2、 保存默认异常处理Handler的引用(Java虚虚拟机的异常日志都会执行回调函数uncaughtException), 注意handler是运行在主线程里! (PS: 为什么不是子线程? 我的理解是子线程默认没有looper, 而创建android进程时AndroidNative.java里会创建一个looper。)
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
3、 将uncaughtException函数的参数转换为字符串, 即异常日志堆栈信息。
4、 为了更好的分析问题,除了异常堆栈外,可能还需要app版本号、用户信息(例如用户名或手机号)、手机信息(如机型、版本等)、当前进程的线程信息等等, 将这些信息保存到数据库表里(可以用原生的sqlite或三方库如Realm、LitePal、OrmLite、GreenDao等等), 写到数据库的目的在于能够多条批量上传,更重要的是避免进程崩溃后日志丢失或者不知道是否已上传的状态。 PS:当然还可以在捕获到异常时保存其它你关心的数据!
5、 上传日志可以单独启动个远程服务(运行在:remote进程,目的是避免占用UI进程资源), 使用观察者模式监听数据库变化或监听当前时间和上次上传日志的时间间隔, 当日志记录条数超出阈值或者超出间隔周期时, 将日志打包成gzip或其它压缩格式并传送到服务器, 服务器存储结果并返回成功时, 客户端删除对应的日志记录。
PS: HTTP交互是在子线程执行的, 可以借助三方库例如OkHttp实现。
思路示例代码(省略了存数据和打包上传的代码):
<pre>
public class TheApplication extends Application {
Thread.UncaughtExceptionHandler mDefaultHandler;
@Override
public void onCreate() {
super.onCreate();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //第一步,获取默认handler
//替换handler, 这是在主线程里执行
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
e.printStackTrace(printWriter);
Throwable cause = e.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String result = writer.toString(); //这就是异常日志堆栈信息
/**
第三步, 存储app版本号、用户信息(例如uid或手机号)、手机信息(例如机型、版本号)、
当前进程的线程信息和result(即异常堆栈到一个数据库表里(状态位表示未上传,已上传时删除该记录)
*/
/**
* 第四步, 上传异常日志的服务器。 进程里应该有个服务,监听着数据库异常日志表(观察者模式)或启动服务
* 时判断记录条数是否达到阈值; 超过阈值时, 将多条记录打包压缩成gzip或其它格式上传到服务器, 服务器
* 存储数据后返回成功, 客户端删除本地日志表的对应记录。
*/
mDefaultHandler.uncaughtException(t, e);
// Java的默认异常处理。 如果是NullPointerException, 注释掉这行app会无响应,因为"AndroidRuntime: Shutting down VM"
}
});</pre>