1. 现象识别:ANR 与磁盘 IO 的基础关联
磁盘 IO 瓶颈是 Android ANR(Application Not Responding)的重要诱因之一。当主线程在限定时间内未完成输入事件分发或关键响应时,系统抛出 ANR。其中磁盘 IO 超时是典型的“慢操作”场景——文件读写、数据库事务、SharedPreferences 写入等若在主线程执行,一旦遇到 IO 拥塞或外存性能短板,就会直接阻塞主线程,触发 watchdog 判定。
1.1 典型表象
| 现象 | 描述 |
|---|---|
| ANR 弹窗 | “App is not responding. Do you want to close it?” |
| 日志标记 | logcat 中出现 ANR in ... (pid xxx),并附带 CPU 负载、IO wait 信息 |
| Trace 栈顶 | 主线程堆栈顶部为 FileOutputStream.write()、SQLiteDatabase.insert() 或 SharedPreferences.commit() |
| 系统 watchdog | Service/Input/ContentProvider 超时阈值(通常 5s/10s/30s) |
1.2 系统 Watchdog 判定阈值
不同 Android 版本对主线程阻塞的容忍时间略有差异,常见阈值如下:
| 超时类型 | 默认时间 | 触发条件 |
|---|---|---|
| Input 事件派发 | 5 秒 | 主线程未处理触屏/按键事件 |
| Service 执行 | 20 秒 | Service 的 onCreate() / onStartCommand() 超时 |
| ContentProvider 发布 | 10 秒 | Provider onCreate() 超时 |
| BroadcastReceiver 处理 | 10 秒 | 主线程中 onReceive() 执行过长 |
1.3 磁盘 IO 与 ANR 的因果链
磁盘 IO 瓶颈直接扰乱主线程的消息循环:
- 主线程发起同步写文件或读数据库 → 发起系统调用
write()/read()。 - 存储介质响应慢(eMMC 队列深度小、碎片化、降频)→ 调用挂起数秒。
- 主线程被阻塞,无法处理后续消息 → 消息队列积压。
- 系统 watchdog 检测到超时 → 发送 SIGQUIT 信号 → 生成 ANR trace。
常见误解:并非所有 ANR 都因 CPU 忙碌造成。磁盘 IO 导致主线程进入 D 状态(不可中断睡眠),CPU 可能空闲,但主线程依旧被阻塞。排查时需特别关注 trace 中的
D 状态及内核调用栈。
判断规则:若 logcat 中
cpu_load 正常(total:0.0% user:0.0% kernel:0.0% iowait:10.5% 中的 iowait 高),且 trace 主线程调用栈包含文件操作,则高度怀疑磁盘 IO 瓶颈。
1.4 排查启动物理视图
以下故障树直观展示从磁盘 IO 到 ANR 的传导路径:
1.5 数据采集准备
遇到疑似磁盘 IO 导致的 ANR,需立即收集以下数据:
- logcat 日志:
adb logcat -b all | grep -E "(ANR|watchdog|iowait)" - ANR trace 文件:
/data/anr/traces.txt(需 root 或 adb bugreport) - systrace / perfetto:捕获 IO 调度和文件操作耗时
- /proc/stat 中的
iowait比例
# 例:实时查看 iowait
adb shell cat /proc/stat | grep "cpu " | awk '{print "iowait: "$6}'
# 例:导出现有 ANR trace
adb bugreport bugreport.zip
unzip -p bugreport.zip FS/data/anr/traces.txt > traces.txt
排查判断表:
| 证据链 | 分值 | 说明 | |--------|------|------| | logcat 中 iowait > 10% | 2 分 | 系统磁盘响应慢 | | trace 主线程栈顶是文件操作 | 3 分 | 直接证据 | | trace 中主线程状态为 D (uninterruptible sleep) | 2 分 | 内核态阻塞 | | 无其他 CPU 繁忙线索 | 1 分 | 排除计算密集型 | | 总分≥5 则高概率为磁盘 IO 瓶颈 | | |
| 证据链 | 分值 | 说明 | |--------|------|------| | logcat 中 iowait > 10% | 2 分 | 系统磁盘响应慢 | | trace 主线程栈顶是文件操作 | 3 分 | 直接证据 | | trace 中主线程状态为 D (uninterruptible sleep) | 2 分 | 内核态阻塞 | | 无其他 CPU 繁忙线索 | 1 分 | 排除计算密集型 | | 总分≥5 则高概率为磁盘 IO 瓶颈 | | |
1.6 修复动作与预防
虽然本章仅介绍基础关联,但可明确后续行动方向:
- 修复:将主线程中的所有同步磁盘 IO 迁移至子线程,使用
AsyncTask、Kotlin Coroutine或HandlerThread。 - 复测:构造高 IO 压力场景(同时读写大文件、频繁数据库事务),验证 ANR 不再出现。
- 预防:
- 使用WorkManager处理后台写操作
- 数据库批量插入使用事务 +setLockingEnabled(false)
- 避免在onResume()、onCreate()中执行大文件 IO