1. ANR简介:什么是ANR、ANR的类型

什么是ANR?先别急着慌

ANR,全称 Application Not Responding,应用程序无响应。说白了,就是你的App卡住了,系统看不下去了,弹个对话框问用户:“是继续等还是关掉?”

我在项目中见过不少新手,一看到ANR就慌,觉得是Bug。其实这是个信号,告诉你某个地方超时了。ANR并不可怕,可怕的是看不懂它为什么发生。

系统判定ANR的标准很直接:主线程在规定时间内没做完该做的事。这个“规定时间”因场景而异,我们随后会细说。

ANR的本质:主线程阻塞,无法响应用户操作。不是内存爆了,不是CPU满了,就是主线程被占着不放。

ANR的四种类型

Android系统定义了四种ANR类型,每种对应不同的超时场景。我整理了一张图,帮你快速建立整体认知:

ANR 类型全景图 ANR 类型 Input Dispatching 超时时间:5s 关键点:事件分发阻塞 Broadcast 超时时间:10s(前台) 60s(后台) Service 超时时间:20s 关键点:onCreate/onStartCommand ContentProvider 超时时间:10s 关键点:发布超时 说明 四种类型对应不同的超时阈值,但核心原因只有一个:主线程被阻塞。 超时时间从 5s 到 60s 不等,取决于具体组件和场景。

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日志时,先搞清楚是哪种类型,然后再往下排查。类型判断对了,问题就解决了一半。