系统如何检测ANR

讲ANR调试,先得弄明白系统是怎么知道你卡了的。说白了,Android系统内部有一套监控机制,就像个后台小哨兵,盯着每个关键任务有没有在规定时间内完成。我刚开始接触ANR时,总以为是代码报错崩了,后来才发现——错了,ANR是超时,不是崩溃。

三种核心超时场景

Android系统主要监控三类场景:

  • 输入事件超时:用户点了屏幕,5秒内没响应,系统就会触发ANR。
  • 广播超时:前台广播10秒,后台广播60秒。嗯,广播处理不当,这是我踩坑最多的地方。
  • 服务超时:前台服务20秒,后台服务200秒。这里要注意,不是你服务跑满了200秒才报,是从你收到onStartCommand开始计时。

检测流程:从超时到生成trace

整个检测流程分四步,我画了一张图帮你理清逻辑:

系统检测ANR流程图 系统检测ANR核心流程 1. 启动超时计时器 2. 监控线程是否完成 完成 → 清除计时器 超时? 否 是 3. ANR触发器(Service) 4. 生成trace文件

关键组件:谁在背后做这件事?

系统层有几个关键角色参与检测:

组件 角色 说明
InputDispatcher 输入事件分发器 用户点击屏幕后,它等待5秒,没响应就触发ANR
BroadcastQueue 广播队列管理 对前台广播计时10秒,后台广播60秒
ActiveServices 服务调度器 跟踪服务超时,前台20秒,后台200秒
AnrHelper ANR辅助类 将超时信息包装,准备写入/traces
ProcessCpuTracker CPU监控 采集各线程CPU使用情况,辅助判断是否真卡死

我个人习惯先查BroadcastQueue的日志,因为广播ANR出现频率最高。我记得有一次线上崩溃,就是后台广播里做了大量IO操作,活生生拖了50多秒。

检测时序:精确到毫秒

系统是怎么精确计时的?每个ANR监测点都挂了一个Handler消息。

  • InputDispatcher在dispatch事件时,postDelayed一个5秒的Runnable。
  • 如果事件处理完,就移除这个Runnable。
  • 如果Runnable被执行了——恭喜,触发ANR。

看一段关键源码逻辑简化版:

// 伪代码:InputDispatcher检测逻辑
void dispatchEvent(InputEvent event) {
    // 记录本次事件,并设置超时
    mPendingEvent = event;
    mHandler.postDelayed(mAnrRunnable, TIMEOUT_INPUT); // 5秒

    // 交给应用处理
    deliverToApp(event);
}

void finishedEvent(InputEvent event) {
    mPendingEvent = null;
    mHandler.removeCallbacks(mAnrRunnable); // 正常完成,移除超时
}

经验技巧: 看/data/anr/traces.txt时,重点关注"main" prio=5 tid=1的线程状态。如果显示BLOCKED或WAITING,问题往往就在那里。我曾经通过这个字段,一把揪出某个锁等待导致的ANR。

ANR触发后:系统会做什么?

一旦超时确定,系统执行以下操作:

  1. 写入traces.txt: 在/data/anr/目录生成堆栈信息。每个线程的调用栈都在这。
  2. 弹出ANR对话框: 前台应用会有个"应用无响应"的弹窗,用户可以选择等待或关闭。
  3. 输出logcat: 可以看到ANR in com.example.app这样的日志。
  4. 可能触发降级: 如果系统负载过高,ANR还可能引起SIGQUIT信号,直接dump所有线程。

避坑指南: 我曾经发现一台测试机频繁ANR但/data/anr/traces.txt为空。后来才搞清楚——是系统存储空间不足,写trace文件失败了。所以排查时,记得先确认存储是否正常。

你以为的超时,可能不是真的超时

系统还做了个聪明的判断:CPU是否真的在忙活?

如果主线程被BLOCKED或者WAITING,CPU实际是空闲的,系统可以判定这是真ANR。但如果主线程在跑一个死循环,CPU占满了,系统也需要等待超时时间到达。你想想看,这两种情况的trace文件内容完全不一样。

  • CPU占用高且主线程正执行: 堆栈指向你的业务代码行。
  • CPU占用低且主线程在等锁: 堆栈会看到waiting to lock或parking等信息。

小结:检测机制的两条腿

系统检测ANR靠两条腿走路:

一条是超时计时器: 精确到毫秒的handler消息机制。

另一条是CPU采样: 通过ProcessCpuTracker每隔一段时间采集各线程CPU使用率,辅助判断是否真的卡死。

这两条腿配合,几乎不会漏报。但偶尔也会误报——比如系统负载极高时,输入事件5秒处理完但被线程调度延迟了。嗯,这种场景比较少见,遇到了再去深入分析。