动态交付性能监控与ANR治理案例 - 第1节

一、什么是ANR?你真的了解它吗?

ANR,全称 Application Not Responding。翻译过来就是“应用无响应”。

说白了,就是你的 App 僵住了。系统看你 5 秒内没处理完输入事件,直接弹个对话框问你“继续等待还是关闭?”。我刚开始做开发时,觉得 ANR 离自己很远。直到有一次,用户反馈登录页面点不动,我一看日志,好家伙,主线程在读文件——典型的“不准时响应”。

核心结论:ANR 是系统层面的保护机制,专门用来“制裁”那些长时间霸占主线程不干活的行为。

1.1 ANR 的触发条件

系统判定 ANR,主要看这三个场景:

  1. 输入事件(点击、滑动)响应超过 5 秒
  2. BroadcastReceiver 的 onReceive() 执行超过 10 秒(前台)/ 60 秒(后台)
  3. 前台 Service 的 onStartCommand() 执行超过 20 秒

你想想看,主线程一旦被阻塞,整个 UI 就停摆了。系统可不管你是在等网络还是算大数,超时就弹窗。我自己的经验是,多数 ANR 并不是单一原因造成的,而是“多种阻塞叠加”。

1.2 动态交付场景下,ANR 更容易出现

动态交付模式,说白了就是“边跑边下载代码和资源”。这种模式天然就有几个“暗坑”:

  • 网络强依赖:代码热修复包、动态资源包的下载,一旦遇上网速卡顿,主线程如果同步等,必然 ANR
  • 资源竞争:多个插件或模块同时尝试加载,抢占主线程的 Looper 时间片
  • 兼容性风险:动态下发的代码可能与宿主环境有冲突,直接导致主线程死锁

我记得有个项目,插件化框架在 App 启动时动态加载一个 so 文件,结果文件下载超时,主线程直接卡死了。这就是典型的动态交付 ANR。

二、为什么动态交付必须做性能监控?

没有性能监控,你就是在“盲人摸象”。用户投诉 ANR,你才去翻日志,到那时黄花菜都凉了。

我个人习惯是:把监控做在问题发生之前。你不需要等到 ANR 弹窗,从卡顿的那一刻就应该被捕获。

2.1 性能监控的核心价值

监控维度 关键指标 监控目标
主线程卡顿 响应时间、帧率 在 ANR 弹窗前 2 秒预警
资源加载 下载耗时、解压耗时 检测“慢加载”链路
内存占用 堆内存、GC 频率 避免因 GC 导致主线程停顿
代码执行 方法耗时、锁竞争 定位“谁占着主线程不放”
我的建议是:从主线程监控入手,用 BlockCanary 的原理解析主线程消息队列。我曾经用这个工具抓到一个隐藏极深的 ANR——动态下发的配置文件解析过程占用了主线程,持续了 3 秒多。

2.2 监控与治理的关系图

很多人以为监控就是“记日志”,不是的。监控是“眼睛”,治理是“手术刀”。下面这张图展示了动态交付场景下,ANR 从发生到被监控治理的全链路关系:

动态交付 ANR 与性能监控关系图 用户操作 系统 ANR 检测 主线程阻塞 动态交付场景 性能监控工具 图例说明 用户操作 → 触发系统 ANR 监控机制 动态交付场景(插件加载、资源下载)是导致主线程阻塞的主要原因 性能监控工具全方位关注主线程、资源、内存,提前发现 ANR 前兆 箭头表示“触发”或“被监控”的关系
注意:不要等用户投诉了才去追查 ANR。性能监控要前置,做到“卡顿可感知、ANR 可预警”。

2.3 实战中如何快速定位 ANR 源头

嗯,这里我要分享一个小技巧。当你在 adb logcat 中看到 ANR 弹窗时,第一时间拉取 /data/anr/traces.txt:

adb pull /data/anr/traces.txt

这个文件里记录了所有线程的堆栈信息。我一般会先看“main”线程的标志,找到它到底卡在哪个方法上。如果发现是动态交付模块的方法,立刻定位到对应的插件或资源加载链路。

我曾经在一个案例里,看到 main 线程阻塞在 PluginLoader.loadPlugin() 方法上。进一步翻看 traces 文件,发现它在一个 synchronized 块里等另一个线程释放锁。那个线程却在等网络下载完成——典型的死锁型 ANR。

所以结论很清晰:动态交付的性能监控,不只是监控主线程,还要监控资源加载、锁竞争、网络延迟。这些因素一交织,ANR 就出来了。