该网页是 Android ANR 系列的第三篇,主要分享了多个 ANR 案例,涵盖死锁、冻结、广播超时等常见问题场景,旨在通过实际案例帮助开发者理解 ANR 的成因与分析方法。以下是详细总结:
一、ANR 常见原因分类
-
应用自身问题
- 死锁:主线程等待锁、调用
join()/sleep()/wait()等方法。 - 主线程阻塞:耗时操作(如复杂布局、IO 读写、数据库操作)、子线程同步锁阻塞。
- 生命周期超时:ContentProvider、Service、广播接收器的回调方法执行超时。
- 渲染问题:渲染线程(RenderThread)耗时导致主线程等待。
- 死锁:主线程等待锁、调用
-
系统或远端进程问题
- Binder 通信耗时:与 SystemServer 通信时服务端处理缓慢或锁竞争。
- 资源竞争:整机低内存、CPU/IO 负载高、SurfaceFlinger 超时。
- 窗口异常:焦点窗口缺失或 Input 事件分发异常。
二、典型 ANR 案例解析
1. 死锁案例:今日头条分屏操作 ANR
-
场景:频繁分屏操作后应用卡死,4-5 秒后恢复。
-
日志关键信息
- 主线程(tid=1)阻塞于等待
LogReaper锁,该锁被线程 34 持有。 - 线程 34(LogReaper)等待
AtomicInteger锁,形成死锁。
- 主线程(tid=1)阻塞于等待
-
结论:应用内多线程锁竞争导致主线程阻塞。
2. 冻结案例:天气应用输入事件超时
-
场景:按下返回键后应用无响应,触发输入 ANR。
-
日志关键信息
- 系统因灭屏(LcdOff)冻结应用(00:51:04),5 秒后输入事件(KEYCODE_BACK 的 ACTION_UP)处理超时。
- 解冻日志(
xxxHansManager: unfreeze)显示应用被系统功耗优化机制冻结。
-
结论:系统冻结机制导致应用无法响应输入事件,需检查冻结逻辑是否异常。
3. 广播超时案例:数据库升级 ANR
-
场景:迁移数据时广播接收器触发 ANR。
-
关键分析
- 广播接收器
onReceive中使用goAsync()开启子线程处理数据库升级,但未在超时内调用PendingResult.finish()。 - 官方文档说明:
goAsync()仅允许异步处理,但广播总耗时(含子线程)仍受超时限制(前台广播 10 秒,后台 30 秒)。
- 广播接收器
-
结论:异步处理未及时结束广播,导致超时。
4. 输入 ANR 案例:Launcher 渲染阻塞
-
场景:Launcher 输入事件分发超时。
-
日志关键信息
- 主线程阻塞于等待渲染线程(RenderThread)完成
dequeueBuffer操作。 - 渲染线程卡在与 SurfaceFlinger 的 Binder 通信,因 Buffer 队列满(无可用 Buffer)导致等待。
- SurfaceFlinger 日志提示 “acquireBuffer: max acquired buffer count reached”。
- 主线程阻塞于等待渲染线程(RenderThread)完成
-
结论:SurfaceFlinger Buffer 资源不足,导致渲染阻塞,需优化图形资源管理。
5. 其他异常案例
- 主线程数据库读写:直接在主线程执行 SQLite 操作,如
SQLiteOpenHelper.getWritableDatabase()阻塞。 - Binder 数据量过大:Binder 通信传输数据超过阈值(如 388KB),触发
Unreasonably large binder reply buffer异常。 - Binder 通信失败:内核日志显示 Binder 事务失败(如错误码 - 3、-22),可能因驱动或内存问题导致。
三、ANR 分析与治理思路
-
分析流程
- 通过
am_anr定位 ANR 时间点,结合ANR in日志分析 CPU / 内存负载。 - 解析
traces.txt线程堆栈,识别阻塞位置(如锁竞争、IO 操作)。 - 追踪系统关键日志(如冻结、Binder 通信、SurfaceFlinger 异常)。
- 通过
-
治理策略
- 应用层面:避免主线程耗时操作、优化锁策略、使用
JobScheduler替代长耗时广播。 - 系统层面:优化 Buffer 队列管理、监控 Binder 线程池状态、调整内存回收策略。
- 应用层面:避免主线程耗时操作、优化锁策略、使用
四、参考资料与工具
- 案例参考:高爷 ANR 优化实践系列文章。
- 分析工具:
adb bugreport、Systrace、Perfetto。 - 关键日志:
am_anr、dvm_lock_sample、binder_sample、SurfaceFlinger异常日志。