异常监控之安卓异常监控设计思路
前言
监控与优化在我们的总是在我们开发中占据着重要的地位,当我们的项目达到了一定的规模,如何去做异常监控就是一个迟早的需求。我们会在这篇文章与大家一起探讨一下安卓常用的异常监控思路。
异常捕获
市面上常见的异常捕获sdk是使用腾讯的Bugly,确实是一个好用的工具,但是有些时候我们还会有很多自己的业务需求,需要做到对除了crash以外的针对业务的个性化日志存储、上报、个性化监控、crash统计等功能,Bugly就不能满足了,而且现在Bugly是免费的,万一以后又要收费怎么办。还有一个重要的问题,假如我们开发的是sdk,需要给第三方使用,万一第三方也接入了Bugly就会和我们产生库冲突,所以,如果自己能实现一套异常捕获系统是最好的。
Crash异常捕获:关于程序异常分很多种异常,其中最常见也是最重要的是应用闪退Crash异常(应用卡顿我忍还能用,直接闪退没法使用那没法忍了)。那我们要如何实现Crash异常捕获呢?我们可以通过接入安卓的UncaughtExceptionHandler接口,重写uncaughtException方法,当未被捕获的Crash发生的时候,就会走到uncaughtException方法,我们可以在uncaughtException方法传入的参数中拿到Crash对应的方法名、报错信息、崩溃堆栈等信息,我们可以先处理完我们想要处理事情(保存日志、上报日志等)然后再让程序闪退。
实现代码如下:
package com.example.mytest;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import java.lang.Thread.UncaughtExceptionHandler;
public class GetCrash implements UncaughtExceptionHandler {
/**
* 系统默认的UncaughtException处理类
*/
private Thread.UncaughtExceptionHandler mDefaultHandler;
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
String className = throwable.getClass().getName(); //获取异常类类名
String message = throwable.getMessage(); //获取异常信息
StackTraceElement[] stack = throwable.getStackTrace(); //获取异常堆栈
StringBuffer stackTrace = new StringBuffer();
for (int i = 0; i < stack.length; i++) {
if (1 == stack.length - 1) {
stackTrace.append(stack[i].toString());
} else {
stackTrace.append(stack[i].toString() + "\n");
}
}
saveLog(className,message,stackTrace); //保存日志
reportException(className,message,stackTrace); //上报异常
SystemClock.sleep(3000); //sleep一会,先让该上报和该保存的操作处理完再闪退
if (mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, throwable);
}
}
/**
* 上报异常信息
*/
private void reportException(String className,String message,StringBuffer stackTrace){
// 实现上报异常信息
}
/**
* 本地日志保存,万一之前的异常没有上报成功,可以下次启动再上报
*/
private void saveLog(String className,String message,StringBuffer stackTrace){
// 实现本地日志保存
}
}
异常上报
在刚刚的异常信息捕获的代码中,有提到需要将异常给上报上来,不然这个捕获就没有意义了。不同的公司可能都会有自己的一套埋点数据上报体系,可以通过调用自己的埋点上报体系去完成异常上报的操作。但是如果是想建立完善的异常监控系统,我的建议最好还是单独针对异常上报开发一套上报体系。从两个方面来看,第一点是针对异常信息的上报需要做后台管理与告警,在后台需要实现计算异常信息发现了多少次、同比或者环比增加了多少、异常信息查询等功能,所以与之前的埋点后台不一定通用,第二点是随着业务的需求,可能异常信息的监控不仅仅是技术上的异常,可能还有业务上的异常,比如登录异常,支付异常、初始化异常、转化情况异常等等复杂的情况,所以为了后续的功能的可拓展与可维护,最好还是把异常情况做成独立的上报体系。上报代码无非是一些上报类型封装与网络请求的的处理,这里就不展开讲了。
异常本地记录
既然我们已经有了异常的上报了,为什么我们还要做异常本地记录呢?因为异常的本地记录是app异常排查必不可少的工具。本地化日志可以:
1.在异常发生的时候如果异常上报没有成功,可以在应用第二次启动的时候读取本地异常日志重新上报。
2.本地异常日志可以帮助我们查看定位特定用户的问题。
3.除了异常的日志我们还可以把一些关键日志也打进日志文件,方便查找问题。
很多时候我们可能会遇到特定用户的诉求,说是应用的某某功能发现了异常,但是我们技术未能定位到先关的问题,这个时候如果我们能拿到用户相关的日志文件,那我们就能更好地定位问题。但是这里需要注意的点是,关于日志的保存我们最好不要通过直接的IO文件读写去保存本地日志,因为每次IO读写系统都需要新开一个线程去处理,像日志读写这种频繁的操作使用IO会比较耗费性能。最好的实现方式是通过内存映射(mmap)来实现。正常的IO操作需要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间),而内存映射(mmap)可以直接通过在物理介质和用户空间之间建立映射,做到只拷贝一次即可完成文件的读写操作,相比IO操作更高效。像安卓的Binder IPC 实现原理就是通过内存映射去实现的。
我们如果要写内存映射接口还是需要一定的技术门槛,写不好的话可能还会给自己制造bug,这里可以使用一些成熟的开源框架,例如腾讯的Xlog。Xlog就是通过内存映射的方式实现的日志保存,附上Xlog官方的github:github.com/Tencent/mar…
结语
其实异常捕获不仅仅只有这些,还会有anr、oom、内存泄漏、内存抖动、卡顿等异常监控,篇幅有限这里就不展开,我们会在后续会继续给大家带来更多的异常捕获的文章。