设计崩溃监控系统
问题
如何设计一个完整的 Android 崩溃监控系统?
答案
整体架构
Java Crash 捕获
class CrashHandler private constructor() : Thread.UncaughtExceptionHandler {
private var defaultHandler: Thread.UncaughtExceptionHandler? = null
fun init(context: Context) {
// 保存系统默认的 Handler,最后交还控制权
defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// 1. 采集崩溃信息
val crashInfo = CrashInfo(
threadName = thread.name,
stackTrace = throwable.stackTraceToString(),
timestamp = System.currentTimeMillis(),
deviceInfo = DeviceInfoCollector.collect(),
memoryInfo = MemoryInfoCollector.collect(),
)
// 2. 持久化到本地(避免在崩溃时发网络请求)
CrashFileWriter.write(crashInfo)
// 3. 交还系统处理(弹出 ANR 对话框等)
defaultHandler?.uncaughtException(thread, throwable)
}
}
Native Crash 捕获
Native Crash(SIGSEGV、SIGABRT 等)无法被 Java 层捕获,需要通过 Signal Handler:
// 使用 Google Breakpad 或 Crashpad
object NativeCrashHandler {
init {
System.loadLibrary("crash_handler")
}
// JNI 方法:注册 Signal Handler,设置 minidump 输出目录
external fun nativeInit(dumpDir: String)
}
// crash_handler.c
#include <signal.h>
#include "client/linux/handler/exception_handler.h"
// Breakpad 回调:崩溃时生成 minidump 文件
static bool DumpCallback(
const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
// minidump 已写入 descriptor.path()
return succeeded;
}
JNIEXPORT void JNICALL Java_NativeCrashHandler_nativeInit(
JNIEnv *env, jobject obj, jstring dumpDir) {
const char *dir = env->GetStringUTFChars(dumpDir, nullptr);
google_breakpad::MinidumpDescriptor descriptor(dir);
// 注册异常处理器
new google_breakpad::ExceptionHandler(
descriptor, nullptr, DumpCallback, nullptr, true, -1);
env->ReleaseStringUTFChars(dumpDir, dir);
}
上报策略
| 策略 | 说明 |
|---|---|
| 延迟上报 | 下次冷启动后延迟 5s 上报,避免影响启动 |
| 去重 | 相同堆栈 hash 24h 内只上报一次 |
| 采样 | 非致命异常按 10% 采样 |
| 回退 | 上报失败本地保留,最多暂存 50 条 |
| 压缩 | Gzip 压缩 + Protobuf 序列化 |
符号化
Release 包的堆栈都是混淆过的。构建时保留 mapping.txt(R8)和 .so 的 debug symbols,上传到服务端用于还原:
- Java 堆栈:
mapping.txt→ ReTrace - Native 堆栈:
addr2line或ndk-stack解析 minidump
常见面试问题
Q1: Java Crash 和 Native Crash 的捕获方式有什么区别?
答案:
| 对比 | Java Crash | Native Crash |
|---|---|---|
| 机制 | Thread.UncaughtExceptionHandler | Signal Handler(SIGSEGV 等) |
| 产物 | 异常堆栈字符串 | Minidump 二进制文件 |
| 还原 | mapping.txt ReTrace | addr2line / ndk-stack |
| 工具 | 自行实现即可 | Breakpad / Crashpad |
Q2: 为什么不在崩溃时直接发网络请求上报?
答案:
崩溃时进程状态不稳定,可能堆损坏、线程中断、fd 耗尽,发网络请求很可能失败或卡死。正确做法是仅做最小化的文件写入(write 系统调用),下次启动时再读取上报。