1. 课程导入

在移动端体验竞争日益激烈的今天,用户对应用的“快”已经形成肌肉记忆:列表要秒开、图片要即现、安装要迅速、后台任务不能拖慢前台。很多团队把优化重心放在CPU、内存和渲染上,却忽略了一个更底层、更容易被误判的瓶颈——IO与存储。一次主线程上的小文件读取、一次未合并的数据库写入、一次不合理的日志刷盘,都可能让帧率骤降、ANR率上升、耗电增加。本课程的目标,是帮助你建立一套从原理认知到工具测量、再到调优落地的完整方法论。

一、学习目标:从“感觉慢”到“可测量、可解释、可优化”

本课程不满足于罗列API或背诵结论,而是希望你在完成学习后具备四项能力:

  • 定位能力:能判断一次卡顿是CPU、内存、锁竞争还是IO等待导致,避免“优化错对象”。
  • 测量能力:熟练使用Systrace/Perfetto、Strace、iostat、Block IO trace、SQLite分析工具等,拿到真实IO路径与耗时分布。
  • 调优能力:掌握文件读写、SharedPreferences、SQLite、Room、日志、图片缓存、安装与启动等场景的优化手段。
  • 权衡能力:理解吞吐、延迟、内存占用、数据安全、电量之间的取舍,不做“局部最优、全局更差”的优化。

核心判断:IO调优的第一原则不是“让IO更快”,而是“让关键路径上少做IO、晚做IO、批量做IO、异步做IO”。

二、核心概念:Android存储栈与IO关键指标

Android的存储路径可以粗略分为:应用层(Java/Kotlin API、NDK)→ 系统服务与库(MediaProvider、SQLite、libc)→ 内核(VFS、文件系统、Block Layer、IO调度器)→ 硬件(UFS/eMMC、闪存控制器)。理解这条链路,才能知道一次write()为什么可能阻塞几百毫秒。

需要重点掌握的概念包括:

  • 顺序IO与随机IO:闪存对随机小IO更敏感,随机写放大和GC会显著拉高延迟。
  • 页缓存与回写:write()返回不代表落盘,脏页回写可能造成后续突发卡顿。
  • fsync/fdatasync:决定数据是否真正持久化,也是很多“偶发卡顿”的元凶。
  • IOPS、吞吐量、延迟:IOPS看小文件与随机访问,吞吐看大文件,延迟看用户体验。
  • 直接IO与缓冲IO:是否绕过页缓存,影响内存占用与一致性。
  • 文件系统与加密:F2FS、ext4、FBE文件级加密都会改变IO行为。

下面这张图展示了从应用到硬件的IO信号链与关键观测点,建议在后续章节中反复对照:

Android IO 信号链与调优观测点 应用层 File / SP / Room 系统库/服务 SQLite / libc 内核 VFS 页缓存 / 回写 文件系统 F2FS / ext4 / FBE 块设备 UFS / eMMC 关键观测点 ① 主线程IO与StrictMode ② SQLite慢查询与事务 ③ 页缓存命中率与脏页回写 ④ fsync频率与延迟 ⑤ 块层IOPS/吞吐/队列深度 ⑥ 闪存写放大与GC 工具映射:Perfetto / Systrace · Strace · iostat · blktrace · SQLite EXPLAIN QUERY PLAN · StrictMode 原理认知 存储栈 / 缓存 / 持久化 测量定位 Trace / 日志 / 指标 调优落地 异步 / 批量 / 缓存 验证复盘 回归 / 灰度 / 防劣化

三、整体知识框架:四层递进

课程内容按“认知—测量—优化—治理”四层展开,每层都对应可落地的产出物:

层次 核心问题 关键内容 产出物
原理认知 IO为什么慢 存储栈、页缓存、fsync、文件系统、闪存特性 IO路径图与瓶颈假设
测量定位 慢在哪里 Perfetto、Strace、iostat、SQLite分析、StrictMode Trace证据与耗时分布
调优落地 怎么改 异步化、批量合并、缓存、事务、日志分级、启动优化 可合并的代码与配置变更
治理复盘 如何不退化 基线指标、CI卡口、灰度监控、复盘机制 防劣化规范与监控看板

四、实战路线图:从一次卡顿开始

假设你遇到一个真实场景:用户反馈“进入订单列表页偶尔卡顿1秒”。推荐的实战路线如下:

  1. 复现与抓取:用Perfetto抓取卡顿区间,确认主线程是否处于D状态或fsync等待。
  2. 定位IO点:结合Strace查看系统调用,确认是数据库查询、文件读取还是日志写入。
  3. 量化影响:统计IO次数、单次耗时、总耗时,判断是“次数多”还是“单次慢”。
  4. 选择策略:能缓存则缓存,能异步则异步,能批量则批量,必须持久化再考虑fsync合并。
  5. 验证回归:对比优化前后P90/P99耗时、ANR率、电量与内存,确认没有引入新问题。
# 示例:抓取IO相关trace的常用命令
adb shell atrace -t 10 -b 8000 sched freq idle am wm gfx view binder_driver \
  hal res dalvik rs bionic power pm ss database network adb

# 查看块设备IO统计
adb shell iostat -x 1 5

# 跟踪进程文件系统调用
adb shell strace -f -tt -T -e trace=file,fsync,fdatasync -p <pid>

学习建议:每学完一个调优手段,都回到这张信号链图上标注它作用于哪一层、减少了哪类IO、代价是什么。这样知识不会碎片化。

五、常见误区与复盘提醒

  • 误区一:把IO慢当成CPU慢。主线程D状态、fsync等待、锁竞争都可能表现为“卡”,必须用Trace区分。
  • 误区二:只优化单次耗时,忽略调用次数。1000次1ms的IO同样会造成严重卡顿,批量与缓存往往收益更大。
  • 误区三:盲目使用apply()或异步就认为安全。异步写仍可能堆积、触发回写风暴,需要控制频率与数据量。
  • 误区四:忽略低端机与老化闪存。同一份代码在UFS 2.1与UFS 4.0上表现差异巨大,基线要分设备分层。
  • 误区五:优化后不做回归。IO优化常影响数据一致性与启动逻辑,必须用自动化与灰度验证。

复盘要点:任何IO调优都要回答三个问题——关键路径上是否必须做?能否延后或合并?失败或掉电时数据是否仍然正确?

本课程后续将沿着这条路线,逐层拆解文件IO、SharedPreferences、SQLite/Room、日志、图片与启动安装等典型场景。你现在需要建立的,是一张“从应用到闪存”的全局地图,以及“先测量、再假设、后优化、必验证”的工作习惯。只要坚持用证据驱动优化,IO与存储性能就不再是玄学,而是一组可被工程化解决的问题。