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()
系统 watchdogService/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 瓶颈直接扰乱主线程的消息循环:

  1. 主线程发起同步写文件或读数据库 → 发起系统调用 write()/read()。
  2. 存储介质响应慢(eMMC 队列深度小、碎片化、降频)→ 调用挂起数秒。
  3. 主线程被阻塞,无法处理后续消息 → 消息队列积压。
  4. 系统 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 的传导路径:

磁盘 IO 瓶颈 → ANR 故障树 磁盘 IO 瓶颈 IO 拥塞 / 队列深 写放大 / 碎片化 降频 / 温控 主线程同步 IO 阻塞 消息队列积压 Watchdog 超时判定 ANR 弹窗 排查重点:trace 中 D 状态文件操作

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 瓶颈 | | |

1.6 修复动作与预防

虽然本章仅介绍基础关联,但可明确后续行动方向:

  1. 修复:将主线程中的所有同步磁盘 IO 迁移至子线程,使用 AsyncTask、Kotlin Coroutine 或 HandlerThread。
  2. 复测:构造高 IO 压力场景(同时读写大文件、频繁数据库事务),验证 ANR 不再出现。
  3. 预防:
    - 使用 WorkManager 处理后台写操作
    - 数据库批量插入使用事务 + setLockingEnabled(false)
    - 避免在 onResume()、onCreate() 中执行大文件 IO