第1章 ANR机制与主线程阻塞原理
1.1 主线程Looper模型
Android主线程(UI线程)依赖Looper+MessageQueue+Handler三元组驱动消息循环。每个应用程序进程的主线程在ActivityThread.main()中调用Looper.prepareMainLooper()和Looper.loop(),进入无限循环等待消息。三个核心角色分工如下:
- Looper – 负责从MessageQueue中取出消息并分发,一个线程仅有一个Looper。
- MessageQueue – 存放消息的优先级队列,支持同步屏障(Barrier)和异步消息机制。
- Handler – 发送消息并指定处理代码(handleMessage),与Looper所在线程绑定。
当主线程正在处理一个消息时(如执行handleMessage回调),所有后续消息都会被阻塞在队列中。如果处理时间超过系统规定的阈值,就会触发ANR。
1.2 消息队列工作机制
MessageQueue内部采用单链表存储Message,按when时间戳排序。关键API流程如下:
Handler.enqueueMessage()– 将Message插入队列合适位置。Looper.loop()– 无限调用MessageQueue.next()获取下一条消息。MessageQueue.next()– 若队首消息未到期则阻塞(Native层的epoll等待);若有同步屏障则跳过所有同步消息,只取异步消息。Handler.dispatchMessage()– 分发消息,执行handleMessage()或Runnable.run()。
1.3 ANR触发阈值
系统根据组件类型设置不同的超时阈值,超过该时间主线程仍未响应则判定ANR:
| 组件类型 | 超时阈值 | 触发场景 |
|---|---|---|
| Input事件 | 5秒 | 触摸、按键事件分发未完成 |
| BroadcastReceiver | 10秒 | onReceive() 未返回(前台广播) |
| Service | 20秒 | onCreate() / onStartCommand() 未返回 |
| ContentProvider | 10秒 | 发布时未完成 onCreate() |
注意:从Android 12起,部分阈值可动态调整,但默认值仍以上表为准。日志中可通过 am_anr 事件追踪。
1.4 ANR判定流程
系统通过ActivityManagerService (AMS)内的AppNotRespondingDialog机制判定ANR,主要步骤:
- 当主线程处理输入事件或广播等关键操作时,AMS设置一个超时监控(设置定时器)。
- 若超时前该操作完成,则取消监控;若超时未完成,AMS会检查当前CPU使用情况:如果CPU使用率很高且主线程正在执行代码(非空闲),则判定为主线程阻塞引发ANR;如果CPU空闲或主线程处于Native层等待,可能反映为死锁或Binder调用超时。
- AMS最终向用户弹出ANR对话框,并生成
/data/anr/traces.txt/traces.proto文件,记录所有线程堆栈。
关键命令: 查看历史ANR日志:
adb shell dumpsys dropbox --print data_app_anr | head -50
实时监控ANR发生:
adb logcat -b events | grep am_anr
主线程消息循环与ANR触发流程
实践要点: 避免主线程阻塞的核心是将耗时操作异步化。例如使用
HandlerThread、AsyncTask(已废弃,推荐Coroutines或ExecutorService)处理I/O与复杂计算;对于初始化缓存,可在Application.onCreate()中启动后台线程预加载,并延迟替换主线程引用的缓存对象(详见后续章节)。
常见误判: 不要认为只有UI事件处理才会引发ANR。Service的
onStartCommand耗时超过20秒、ContentProvider发布时onCreate超过10秒同样会触发ANR且不弹出UI对话框,而是直接触发系统重启或降级。