第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流程如下:

  1. Handler.enqueueMessage() – 将Message插入队列合适位置。
  2. Looper.loop() – 无限调用MessageQueue.next()获取下一条消息。
  3. MessageQueue.next() – 若队首消息未到期则阻塞(Native层的epoll等待);若有同步屏障则跳过所有同步消息,只取异步消息。
  4. Handler.dispatchMessage() – 分发消息,执行handleMessage()或Runnable.run()。

1.3 ANR触发阈值

系统根据组件类型设置不同的超时阈值,超过该时间主线程仍未响应则判定ANR:

组件类型超时阈值触发场景
Input事件5秒触摸、按键事件分发未完成
BroadcastReceiver10秒onReceive() 未返回(前台广播)
Service20秒onCreate() / onStartCommand() 未返回
ContentProvider10秒发布时未完成 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触发流程

主线程消息循环与ANR触发流程 主线程启动 Looper.prepare() Looper.loop() MessageQueue.next() Handler处理消息 超时监控(AMS) ANR触发 处理完成(取消监控) ANR判定条件 1. 超时(5s/10s/20s) 2. 主线程正在执行代码 3. CPU未空闲 / 无Binder卡顿
实践要点: 避免主线程阻塞的核心是将耗时操作异步化。例如使用HandlerThread、AsyncTask(已废弃,推荐Coroutines或ExecutorService)处理I/O与复杂计算;对于初始化缓存,可在Application.onCreate()中启动后台线程预加载,并延迟替换主线程引用的缓存对象(详见后续章节)。
常见误判: 不要认为只有UI事件处理才会引发ANR。Service的onStartCommand耗时超过20秒、ContentProvider发布时onCreate超过10秒同样会触发ANR且不弹出UI对话框,而是直接触发系统重启或降级。