Android Crash分类:Java层Crash、Native层Crash、ANR

做Android开发这么多年,我遇到过各种各样的崩溃。说白了,Crash不是只有一种面孔。你想想看,Java代码出错是一种死法,C++代码崩了是另一种,甚至App没崩但卡死也算一种——对,我说的就是ANR。

我把这些崩溃分成三大类:Java层Crash、Native层Crash和ANR。搞明白它们的区别,才能对症下药。下面这张图可以帮你快速建立整体认知。

Android Crash 三大分类 Java层 Crash Native层 Crash ANR NullPointerException IndexOutOfBounds ClassCastException SIGSEGV (段错误) SIGABRT (abort) JNI 调用崩溃 Input 事件超时 Broadcast 超时 Service 超时 💡 核心区别:Crash 会直接崩溃退出,ANR 不会立即退出但会弹“无响应”对话框 Java与Native Crash 本质都是进程崩溃,但堆栈来源和捕获手段完全不同

Java层 Crash

先说最常见的Java层Crash。说白了,就是Java/Kotlin代码里抛出了未捕获的异常(Unchecked Exception),导致当前线程直接挂掉。如果这个线程是主线程,恭喜你,用户会看到「App已停止运行」的对话框。

我个人习惯把Java Crash分为四类高频场景:

异常类型 典型触发场景 我踩过的坑
NullPointerException 调用空对象的方法或属性 曾经在Fragment重建时忘判null,直接闪退
IndexOutOfBoundsException 访问列表越界 RecyclerView数据更新和UI刷新不同步,下标越界
ClassCastException 强制类型转换错误 Gson反序列化泛型擦除导致类型混淆
IllegalArgumentException 方法参数非法 自定义View构造方法传入了负数尺寸
关键点:Java层Crash可以通过 Thread.setDefaultUncaughtExceptionHandler 全局捕获。但注意——捕获后进程仍然会被杀掉,你只能做「临终保存」操作(写日志、上报),无法阻止App退出。
💡 我的小技巧:线上环境我建议用第三方框架(如ACRA、Firebase Crashlytics)来收集Java异常堆栈,但一定要在Application.onCreate里尽早初始化。我曾因为初始化晚了半秒,漏掉了第一个Activity的异常。

Native层 Crash

Native Crash就麻烦多了。它发生在C/C++代码层,比如系统底层、第三方so库、你自己的NDK代码。崩溃时没有Java异常对象,只有信号(Signal)——比如SIGSEGV(段错误)、SIGABRT(主动触发abort)。

嗯,这里要注意:Native Crash的堆栈不经过Java层,你没法用try-catch捕获。你需要注册一个 信号处理器(Signal Handler),或者用 google-breakpad / crashpad 这样的成熟方案。

举个例子,我曾经接一个视频编辑SDK的Crash,对方只扔给我一个tombstone文件。里面全是寄存器值和内存地址,没有Java堆栈。后来我分析so库的调试符号,发现是一个回调函数里指针被释放后又被使用(Use-After-Free)。

// 注册信号处理器的核心代码(简化版)
#include <signal.h>
#include <unwind.h>

void crash_handler(int sig, siginfo_t* info, void* context) {
    // 1. 获取当前堆栈
    // 2. 写入文件
    // 3. 调用原处理器或exit
    _exit(1);
}

void install_signal_handler() {
    struct sigaction sa;
    sa.sa_sigaction = crash_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &sa, nullptr);
    sigaction(SIGABRT, &sa, nullptr);
}
⚠️ 警告:在信号处理器里不要做复杂操作(malloc、printf、Java调用),因为这本身就会触发信号递归导致死锁。只允许使用 write()、_exit() 等异步安全函数。

Native Crash的定位难度比Java高一个数量级。你需要学会看 tombstone文件,里面包含了寄存器状态、堆栈调用链、内存映射。我建议在开发阶段开启 debuggable + android:extractNativeLibs="true",这样so库会留在磁盘上,堆栈里能直接看到函数名。

ANR(Application Not Responding)

最后说说ANR。它跟Crash不一样——进程还在,但系统觉得你「没响应」了。说白了,就是主线程被阻塞了5秒以上(Input事件超时),或者广播接收器10秒内没执行完,或者服务20秒内没执行完。

ANR不会自动弹Crash对话框,但系统会写 /data/anr/traces.txt 文件,里面记录了所有线程的堆栈。我年轻时遇到一个问题:列表滑动时经常ANR,看trace发现是主线程在做数据库写入——我居然忘了用异步线程。

ANR类型 超时时间 常见原因
Input 派发超时 5秒 主线程做耗时操作(网络、文件IO、复杂计算)
Broadcast 超时 10秒 onReceive里阻塞,或串行广播队列队列任务太多
Service 超时 20秒 onCreate/onStartCommand执行缓慢
ContentProvider 超时 10秒 provider初始化或query执行时间过长
💡 避坑指南:我曾经在线上版遇到大量ANR,但traces.txt里主线程堆栈全是系统方法,查不出原因。后来我加了一个自定义监控:通过 Looper.getMainLooper().setMessageLogging() 打印每条Message的执行耗时,发现是一个第三方推送SDK的初始化在主线程同步读文件导致。换成异步初始化后ANR率直接降为0。

三种Crash的捕获策略对比

最后做个总结。我个人习惯把三种类型的捕获方式记成一张表:

类型 捕获手段 关键文件/日志 处理难度
Java Crash UncaughtExceptionHandler Logcat + 自定义崩溃文件 低
Native Crash Signal Handler / Breakpad tombstone / minidump 高
ANR FileObserver监听trace / 自建监控 /data/anr/traces.txt 中
我的个人建议:Crash治理不要只盯着修复,先搞清楚「Crash分类」——你连是哪一层崩的都不知道,何谈根治?先把线上Crash按Java、Native、ANR打标签,优先解决TOP5。我接手过的项目里,80%的崩溃其实只来自20%的异常类型。