一、crash监控
Crash(应用崩溃)是由于代码异常而导致 App 非正常退出,导致应用程序无法继续使用,所有工作都
停止的现象。发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得
多么优秀,也无法避免 Crash 发生
在 Android 应用中发生的 Crash 有两种类型,Java 层的 Crash 和 Native 层 Crash。这两种Crash 的监控和获取堆栈信息有所不同。
1、Java Crash
Java的Crash监控非常简单,Java中的Thread定义了一个接口: UncaughtExceptionHandler ;用于
处理未捕获的异常导致线程的终止(注意:被catch的异常是捕获不到的),当我们的应用crash的时候,就会走 UncaughtExceptionHandler.uncaughtException ,在该方法中可以获取到异常的信息,我们通
过 Thread.setDefaultUncaughtExceptionHandler 该方法来设置线程的默认异常处理器,我们可以
将异常信息保存到本地然后上传到服务器,方便我们快速的定位问题。
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String FILE_NAME_SUFFIX = ".trace";
private static Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
private static Context context;
public static void init(Context applicationContext) {
context = applicationContext;
defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());
}
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
try {
File file = dealException(t, e);
} catch (Exception exception) {
} finally {
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
}
private File dealException(Thread thread, Throwable throwable) throws JSONException, IOException, PackageManager.NameNotFoundException {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
//私有目录,无需权限
File f = new File(context.getExternalCacheDir().getAbsoluteFile(), "crash_info");
if (!f.exists()) {
f.mkdirs();
}
File crashFile = new File(f, time + FILE_NAME_SUFFIX);
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
pw.println(time);
pw.println("Thread: " + thread.getName());
pw.println(getPhoneInfo());
throwable.printStackTrace(pw); //写入crash堆栈
pw.flush();
pw.close();
return crashFile;
}
private String getPhoneInfo() throws PackageManager.NameNotFoundException {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
StringBuilder sb = new StringBuilder();
//App版本
sb.append("App Version: ");
sb.append(pi.versionName);
sb.append("_");
sb.append(pi.versionCode + "\n");
//Android版本号
sb.append("OS Version: ");
sb.append(Build.VERSION.RELEASE);
sb.append("_");
sb.append(Build.VERSION.SDK_INT + "\n");
//手机制造商
sb.append("Vendor: ");
sb.append(Build.MANUFACTURER + "\n");
//手机型号
sb.append("Model: ");
sb.append(Build.MODEL + "\n");
//CPU架构
sb.append("CPU: ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sb.append(Arrays.toString(Build.SUPPORTED_ABIS));
} else {
sb.append(Build.CPU_ABI);
}
return sb.toString();
}
}
复制代码
2、NDK Crash
1)、Linux信号机制
信号机制是Linux进程间通信的一种重要方式,Linux信号一方面用于正常的进程间通信和同步,另一方
面它还负责监控系统异常及中断。当应用程序运行异常时,Linux内核将产生错误信号并通知当前进
程。当前进程在接收到该错误信号后,可以有三种不同的处理方式。
- 忽略该信号;
- 捕捉该信号并执行对应的信号处理函数(信号处理程序);
- 执行该信号的缺省操作(如终止进程);
当Linux应用程序在执行时发生严重错误,一般会导致程序崩溃。其中,Linux专门提供了一类crash信
号,在程序接收到此类信号时,缺省操作是将崩溃的现场信息记录到核心文件,然后终止进程。
常见崩溃信号:
- SIGSEGV 内存引用无效。
- SIGBUS 访问内存对象的未定义部分。
- SIGFPE 算术运算错误,除以零。
- SIGILL 非法指令,如执行垃圾或特权指令
- SIGSYS 糟糕的系统调用
- SIGXCPU 超过CPU时间限制。
- SIGXFSZ 文件大小限制。
一般的出现崩溃信号,Android系统默认缺省操作是直接退出我们的程序。但是系统允许我们给某一个
进程的某一个特定信号注册一个相应的处理函数(signal),即对该信号的默认处理动作进行修改。因
此NDK Crash的监控可以采用这种信号机制,捕获崩溃信号执行我们自己的信号处理函数从而捕获NDK
Crash。
2)、墓碑文件
Android本机程序本质上就是一个Linux程序,当它在执行时发生严重错误,也会导致程序崩溃,然后产
生一个记录崩溃的现场信息的文件,而这个文件在Android系统中就是 tombstones 墓碑文件。
普通应用无权限读取墓碑文件,墓碑文件位于路径/data/tombstones/下。解析墓碑文件与后面的breakPad都可使用 addr2line 工具。
3)、BreakPad
Google breakpad是一个跨平台的崩溃转储和分析框架和工具集合,其开源地址是:github.co
m/google/breakpad。breakpad在Linux中的实现就是借助了Linux信号捕获机制实现的。因为其实现
为C++,因此在Android中使用,必须借助NDK工具。
二、ANR分析简介
anr全称application not responding,应用程序无响应,是通过AMS检测的,分为四种类型
- KeyDispatchTimeout 输入事件无响应,超时时间5s
- BroadcastTimeout 广播超时,前台广播10s,后台广播60s
- ServiceTimeout 服务响应超时,前台服务20s,后台服务200s
- ContentProviderTimeout 内容提供器超时,10s内没有处理完触发
上述四种类型比较特殊的是输入事件超时,其他三种类型如果到了指定的时间没有处理完一定会出发anr,但是输入事件如果到了5s没有处理完不一定触发anr,只要没有下一次输入事件等待处理就不会触发anr,我们平常接触最多的也是输入事件超时;
主要有以下三种原因导致anr
- 在主线程中频繁的进行耗时IO操作,比如文件读写、数据库操作、操作sp等
- 多线程下主线程被死锁
- 系统资源被耗尽,比如cpu、io、管道等资源
anr问题解决主要方法
我们在开发过程中发生anr,我们可以结合logcat日志以及trace文件定位问题发生的地方,然后根据业务具体情况具体分析;我们在分析trace文件时注意几个点
- 如果CPU的使用量很高,说明当前设备很忙,可能是CPU饥饿导致了anr
- 如果cpu使用很低,说明主线程可能被block住了
- 如果IOwait占比很高,那很大可能就是在主线程进行了IO耗时操作
anr线上监控方法
- 通过watchdog
- 通过FileObserver监听data/anr文件夹的变化
anr触发的原理,一个事件发生后,不管是广播还是服务还是输入事件,都会在system_server进程中AMS中埋下一条记录,然后去处理这个事件,如果在规定的时间内处理完毕,则把这条记录消除,如果没有处理完,就会通过引爆这条记录,触发anr。
开发过程中我们要保持良好的习惯,UI线程不要执行耗时的操作,耗时的操作去放到子线程中处理,然后通过handler在子线程和UI线程之间通信,子线程处理完了通过Handler通知到UI线程即可;