跳到主要内容

设计崩溃监控系统

问题

如何设计一个完整的 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)和 .sodebug symbols,上传到服务端用于还原:

  • Java 堆栈:mapping.txt → ReTrace
  • Native 堆栈:addr2linendk-stack 解析 minidump

常见面试问题

Q1: Java Crash 和 Native Crash 的捕获方式有什么区别?

答案

对比Java CrashNative Crash
机制Thread.UncaughtExceptionHandlerSignal Handler(SIGSEGV 等)
产物异常堆栈字符串Minidump 二进制文件
还原mapping.txt ReTraceaddr2line / ndk-stack
工具自行实现即可Breakpad / Crashpad

Q2: 为什么不在崩溃时直接发网络请求上报?

答案

崩溃时进程状态不稳定,可能堆损坏、线程中断、fd 耗尽,发网络请求很可能失败或卡死。正确做法是仅做最小化的文件写入(write 系统调用),下次启动时再读取上报。

相关链接