1. ANR简介:什么是ANR、ANR的类型
什么是ANR?先别急着慌
ANR,全称 Application Not Responding,应用程序无响应。说白了,就是你的App卡住了,系统看不下去了,弹个对话框问用户:“是继续等还是关掉?”
我在项目中见过不少新手,一看到ANR就慌,觉得是Bug。其实这是个信号,告诉你某个地方超时了。ANR并不可怕,可怕的是看不懂它为什么发生。
系统判定ANR的标准很直接:主线程在规定时间内没做完该做的事。这个“规定时间”因场景而异,我们随后会细说。
ANR的本质:主线程阻塞,无法响应用户操作。不是内存爆了,不是CPU满了,就是主线程被占着不放。
ANR的四种类型
Android系统定义了四种ANR类型,每种对应不同的超时场景。我整理了一张图,帮你快速建立整体认知:
1. Input Dispatching Timeout
这玩意儿最常见,也最让用户抓狂。你想啊,用户点了个按钮,5秒没反应,系统就爆了。
触发条件:系统向App分发输入事件(触摸、按键等),5秒内App没有返回处理结果。
我个人遇到过这样一个案例:一个列表页面的滑动事件频繁触发ANR。最后定位到,是某个自定义View的onTouchEvent里做了大量的文件读写操作。你想想看,主线程上读写文件,不卡才怪。
| 关键信息 | 说明 |
|---|---|
| 超时时间 | 5秒 |
| 触发源 | 触摸事件、按键事件、轨迹球事件 |
| 典型日志关键行 | Input dispatching timed out |
| 排查方向 | 检查主线程的Message Queue,看哪个消息执行时间过长 |
注意:Input ANR不一定全是代码问题。我也碰到过低端设备CPU降频导致的事件分发延迟。别一上来就喷代码,先看看系统负载。
2. Broadcast Timeout
广播超时分两种情况:前台广播和后台广播。前者10秒,后者60秒。为什么差异这么大?后台广播系统会给更多宽容,毕竟不能太影响用户体验。
触发条件:onReceive方法在规定时间内没有执行完毕。
我曾经踩过一个坑:在广播接收器里直接发起网络请求,想着“反正异步的,没事吧”。结果呢?网络请求倒真是异步,但主线程还在处理其它的同步操作,加起来超过了10秒,ANR就来了。
小技巧:如果你必须在BroadcastReceiver里做耗时操作,用goAsync()拿到一个PendingResult,然后另开线程执行。执行完记得调用finish(),否则系统会一直等着。
| 场景 | 超时时间 | 典型日志 |
|---|---|---|
| 前台广播 | 10秒 | Broadcast of Intent ... timed out |
| 后台广播 | 60秒 | Background execution ... timed out |
3. Service Timeout
Service超时相对少见一些,但一旦出现,往往比较隐蔽。它不像Input那样一卡就爆,而是在Service的生命周期方法里耗着了。
触发条件:Service的onCreate、onStartCommand、onBind等方法在20秒内没有执行完毕。注意,是执行完毕,不是返回。
我记得有一次排查一个问题:一个音乐播放服务的onCreate里初始化了数据库、网络连接、还有一堆第三方SDK。初始化逻辑加起来跑了25秒,Service ANR就出来了。解决方案是把初始化拆分成懒加载,优先启动Service,然后再逐步初始化。
核心要点:Service的20秒超时是从系统调用onCreate开始计算的。即使你开了新线程,主线程上的onCreate方法必须及时返回。千万别在main线程上做service初始化时搞耗时操作。
4. ContentProvider Timeout
ContentProvider ANR,这个最容易被忽略。很多人觉得“ContentProvider就是个数据源,怎么也会ANR?”
触发条件:ContentProvider发布(publishing)超时,即10秒内没有完成onCreate方法的执行。注意,这是ContentProvider本身的超时,不是CRUD操作的超时。
CRUD操作如果耗时,系统不会弹ANR对话框,而是阻塞调用方线程。但ContentProvider的onCreate一旦超时,整个进程都可能受影响。
我在项目里见过这样的问题:一个ContentProvider的onCreate里做了大量的数据库迁移操作,数据量一大,10秒根本不够。迁移逻辑应该放在独立的Service里执行,onCreate只做必要的初始化。
| 场景 | 超时时间 | 典型日志 | 注意点 |
|---|---|---|---|
| ContentProvider发布超时 | 10秒 | ContentProvider ... didn't create within ... |
onCreate中不要做耗时初始化 |
| CRUD操作超时(间接) | 取决于调用方 | 无ANR日志 | 调用方线程会一直阻塞,可能导致其他ANR |
总结一下四种类型的关键差异
四种类型虽然都会引发ANR,但各自的触发点和排查思路不一样。我把核心差异整理成一个表,方便你对照:
| 类型 | 超时时间 | 触发位置 | 排查重点 |
|---|---|---|---|
| Input Dispatching | 5秒 | 事件分发 | 主线程上的触摸/按键处理逻辑 |
| Broadcast | 10秒/60秒 | onReceive方法 | 是否在onReceive中做了耗时操作 |
| Service | 20秒 | onCreate/onStartCommand | 启动时是否进行了大量初始化 |
| ContentProvider | 10秒 | onCreate(发布时) | 数据库或文件初始化是否过重 |
一句话总结:所有ANR都指向同一个敌人——主线程阻塞。不同类型的区别只在于“谁捅破了这层窗户纸”。
好了,ANR的类型先说到这儿。记住这张图和这个表格,碰到ANR日志时,先搞清楚是哪种类型,然后再往下排查。类型判断对了,问题就解决了一半。