Android系统ANR机制源码解析
ANR(Application Not Responding)说白了就是系统觉得你的App“卡死”了。我在项目早期调试一个视频播放器时,第一次抓到ANR日志,看到“Input dispatching timed out”还以为是自己代码写崩了。后来跟了几遍源码才明白——ANR其实是AMS(ActivityManagerService)和InputDispatcher联手搞的一套“看门狗”机制。
今天我就带你把这套机制的源码脉络理一遍。不要求你记住每一行代码,但核心逻辑和关键节点必须印在脑子里。原因很简单——你不懂系统怎么判定ANR,就永远写不出精准的埋点。
三大触发场景
Android系统会在三种场景下判定ANR:
- Service超时:前台Service 20秒,后台Service 200秒没执行完onStartCommand。
- Broadcast超时:前台广播10秒,后台广播60秒没从onReceive返回。
- Input超时:触摸/按键事件5秒内没被处理完。
嗯,这里要注意:ContentProvider 和 JobScheduler 也会触发ANR,但系统没有单独的超时类型,它们通常表现为Service或Input超时。
源码入口:AMS中的调度器
所有超时检测的起点都在 ActivityManagerService 里。我记得第一次看这个类时,被它那几千行的代码吓到过。其实它内部用了一堆 Handler 和 Message 来管理超时。
比如Service超时的核心逻辑在 ServiceRecord 里:
// ServiceRecord.java (简化)
void startTimeoutLocked(long now) {
if (timeout != null) {
mAm.mHandler.removeCallbacks(timeout);
}
timeout = () -> {
synchronized (mAm) {
// 检查是否仍在执行超时限制内
if (isExecutingRemotely()) {
mAm.appNotResponding(proc, this, null, null, false, "service timeout");
}
}
};
// 前台20秒,后台200秒
long delay = foreground ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT;
mAm.mHandler.postDelayed(timeout, delay);
}
这个 appNotResponding() 就是ANR爆发点。它会做三件事:
- 收集所有线程的stack trace(通过
Process.myTid()和信号机制) - 生成
traces.txt文件 - 弹出ANR对话框或者直接杀进程
ServiceRecord.startTimeoutLocked() → AMS.appNotResponding() → Process.sendSignal() → Debug.dumpNativeAndJavaTraces()
输入超时的特殊之处
Input方面的ANR和AMS其实分属两条线。它由 InputDispatcher 在native层直接检测。当事件分发到窗口后,如果5秒内没有收到“处理后”的回执,InputDispatcher会调用 InputManagerService 的回调,最终也走到 AMS.appNotResponding()。
我自己调试时就踩过一个坑:Input ANR的日志里经常写“Waiting because the touched window has not finished handling input events”,很多人以为是主线程卡,但实际上可能是Binder线程池满了导致消息发不出去。
ANR类型对比表格
| 类型 | 超时时间 | 触发处理器 | 典型堆栈特征 |
|---|---|---|---|
| Service | 前台20s / 后台200s | AMS.mHandler | ActivityManagerService.waitForService |
| Broadcast | 前台10s / 后台60s | BroadcastQueue.mHandler | BroadcastQueue.processNextBroadcast |
| Input | 5s | InputDispatcher (native) | InputDispatcher.dispatchOnce |
| ContentProvider | 10s (通常) | AMS | ProviderRefCount.wait |
traces.txt 里主线程的 java.lang stack。如果主线程在等待锁(比如 BinderProxy.transact() 或 Object.wait()),那多半不是主线程慢,而是另一个线程太慢导致它被堵住了。
ANR机制全流程(SVG)
避坑指南:别被“无响应”骗了
我曾经遇到过一件怪事:App明明在正常滑动,却弹出了ANR对话框。翻看 traces.txt,主线程停在 InputMethodManager 的binder调用上。后来发现是系统输入法服务重启了,导致binder返回超时。这种ANR其实和App性能无关,是系统侧的问题。
所以我的建议是:不要一看到ANR就往自己代码上找原因。先分清楚是系统服务超时还是App主线程阻塞。怎么分?看 traces.txt 里的“why”字段——如果是 Input dispatching timed out,重点查Input Channel;如果是 executing service,去查那个Service的生命周期。
JobScheduler 都无法豁免。我建议你早点加上 android:foregroundServiceType 并启动前台通知,否则用户手机静置几分钟就可能弹出ANR。
一点源码阅读经验
如果你是第一次看 ActivityManagerService 源码,很容易被“谁调用了谁”绕晕。我个人的读法:先找到 appNotResponding() 方法,倒着往上找出触发它的所有路径。大体上就三条:Service超时、Broadcast超时、Input超时。把这三条链分别啃下来,整体脉络就清晰了。
另外,traces.txt 的生成其实是靠 Process.sendSignal(pid, SIGNAL_QUIT) 触发的。你可以在终端手动执行 adb shell kill -3 [pid] 立刻触发一次stack dump,效果完全一样。我调试时经常用这招手动“模拟ANR”,不看对话框,只抓堆栈。