Crash与ANR基础概念:先搞清楚这两个“敌人”
做Android开发这么多年,我遇到最多的线上问题就两个:Crash 和 ANR。很多新人容易把它们混为一谈,觉得都是“应用挂了”。但在我看来,这两者从根上就不是一回事。今天咱们就把这个概念彻底掰扯清楚。
什么是Crash?
Crash,说白了就是应用主动或被动地“自杀”了。当代码里抛出了一个未被捕获的异常(比如 NullPointerException、IndexOutOfBoundsException),或者发生了 native 层的致命信号(SIGSEGV),系统就会直接把你的进程杀掉。用户看到的就是“抱歉,应用已停止运行”。
我记得刚入行那会儿,有一次线上突然爆了一堆 ConcurrentModificationException。查了半天,原来是多线程同时遍历同一个 ArrayList,又没有做同步处理。嗯,这个坑很多老手也踩过。
Crash 的核心特征:
- 进程被强制终止,所有资源释放
- 用户感知明显:弹窗、闪退
- 通常有明确的异常堆栈可追溯
- 对数据的影响:如果正在写数据库或文件,数据可能不完整
什么是ANR?
ANR 的全称是 Application Not Responding,也就是应用无响应。它跟 Crash 不同——进程没有被杀死,只是系统觉得你“卡住了”,如果你不处理,系统过一会儿会弹一个“应用无响应”的对话框,让用户选择等待还是关闭。
为什么会这样?你想想看,Android 系统有个“看门狗”机制。它会监视主线程(UI 线程)的响应情况。如果主线程在触摸事件 5 秒内没有处理完,或者广播接收器 10 秒没返回,或者服务 20 秒没执行完,系统就会认为该线程“僵死”了。
我做过一个数据上传功能,在 onReceive 里直接做网络请求,结果线上 ANR 率飙升。后来才明白:广播接收器默认跑在主线程,网络请求一卡就是十几秒,系统不弹 ANR 才怪。
注意:ANR 并不会自动杀死你的进程。如果用户选择“等待”,应用还能继续运行;如果选择“关闭”,才会被 kill。这跟 Crash 的“当场毙命”有本质区别。
两者的根本区别
好,现在我们把核心差异摆出来。用一张表格可能更直观:
| 对比维度 | Crash | ANR |
|---|---|---|
| 触发原因 | 未捕获异常 / native 信号 | 主线程超时未响应(5s/10s/20s) |
| 进程状态 | 立即被系统杀死 | 进程存活,只是主线程被阻塞 |
| 用户感知 | 闪退 / 停止运行弹窗 | 界面卡死 / 系统无响应对话框 |
| 数据影响 | 写操作可能中断,数据丢失或损坏 | 如果正在写数据,超时后可能处于中间状态 |
| 日志特征 | Java 堆栈 + 异常类型 | ANR trace 文件中记录了主线程堆栈 |
从数据保护的角度看,两者都危险。但危险的性质不同:Crash 是“突然死亡”,数据写到一半就没了;ANR 是“假死”,用户可能耐心等,也可能主动关掉应用。如果用户在 ANR 对话框上点了“确定”,进程被杀死,那数据状态跟 Crash 没什么两样。
一个生活中的比喻
我经常跟团队新人这样讲:
- Crash 就像你开车时发动机突然爆缸,车当场趴窝,没法修复,只能拖走。
- ANR 就像你开车时油门踩到底但车不动,发动机还在转,但变速箱卡住了。你可以等,也可以熄火重启。
虽然效果都是车停了,但原因、处理方式和后果完全不同。
知识结构图
下面这张图能帮你快速理清 Crash 和 ANR 的关系,以及它们对数据保护带来的风险点:
为什么区分这么重要?
如果你正在做一个数据保护框架,或者要监控线上问题,那必须搞清楚到底发生了什么。举个实际场景:
我曾经维护过一个支付模块,用户反馈偶尔会“付了钱但订单没生成”。一开始怀疑是 Crash,查日志发现并没有异常堆栈。后来抓了 ANR trace,发现是支付回调里做了一次全量数据库同步,耗时超过 10 秒,系统弹了 ANR,用户一怒之下点了关闭。数据库里事务回滚了,订单当然没写进去。
你看,如果只监控 Crash,这个问题的根因根本找不到。所以,Crash 和 ANR 在监控体系里必须分开对待。
实战建议:我个人的做法是,在线上监控平台同时采集两个指标:Crash 率(按类型归类)和 ANR 率(按触发场景归类)。并且针对关键数据操作(比如写文件、写数据库)加一个超时保护,避免 ANR 期间数据处在不一致状态。
小结
这一章我们说了很多基础的东西,但别小看它们。我见过不少团队花大把精力去优化 Crash,却对 ANR 不闻不问,结果用户流失反而更严重。记住:
- Crash 是进程被系统杀掉,异常堆栈清晰
- ANR 是主线程卡死,进程还在,但用户可能手动 kill
- 两者都会导致数据处于不可预期的状态,都需要监控