二、快照文件基础:.snap 文件结构,如何手动查看
说实话,我第一次拿到 .snap 文件时也是一头雾水。那是刚接手一个 Compose 项目,CI 上报了一个快照回归,但我死活看不出两个截图差异在哪。后来才发现——我根本不会看 .snap 文件本身。
别笑,我当时以为 .snap 就是个压缩包,解压就能看到图片。结果呢?它根本不是图片,而是一份 序列化的 Compose 语义树。你想想看,如果快照对比失败,你连文件结构都看不懂,怎么定位问题?
.snap 到底是什么?
每个 .snap 文件都是二进制序列化产物,它完整记录了某一时刻 Compose 界面的“快照”:
- 组件树结构(Row、Column、Text 等)
- 修饰符链(Modifier.padding、Modifier.size 等)
- 布局信息(宽高、位置、padding)
- 文本内容与样式
- Compose 版本元数据
我在项目中遇到过两次因为 Compose 版本升级导致 .snap 文件结构偏移的坑。那时候大家只看图片差异,结果忽略了底层结构不一致——白白浪费两天排查时间。
核心结构一览
| 字段 | 类型 | 说明 |
|---|---|---|
| magicNumber | int64 | 魔数,验证文件合法性 |
| version | int | Compose 快照库版本 |
| components | List<Node> | 根节点及子组件树 |
| modifiers | List<ModifierData> | 每个节点关联的修饰符链 |
| layoutInfo | Map<Int, Rect> | 节点 ID 到具体大小位置的映射 |
| pixelData | byte[] | 实际像素缓冲(非压缩图片) |
你看,它其实是一个结构清晰的记录表。但问题是——二进制文件怎么手动查看?
手动查看的三种方式
1. 用 Android Studio 的 Device File Explorer
最省事的方法。连设备或模拟器后,在 /data/data/包名/files/compose_snapshots/ 下找到 .snap 文件。双击就能在 AS 里看到结构化的 JSON 树视图。我曾经在这儿一眼看出某个 Text 节点的字体大小被莫名覆盖了——那时候我差点以为是自己代码 bug。
2. 用命令行配合 protobuf 工具
.snap 底层使用的是 Protocol Buffers(protobuf)。你可以写个简单的脚本反序列化它。我个人习惯这么干:
# 安装 protobuf 编译器
brew install protobuf
# 用 protoc 反序列化(需要 .proto 文件)
protoc --decode_raw < snapshot.snap > snapshot.txt
不过要注意,Compose 的 .proto schema 经常更新,用 --decode_raw 可能会丢失字段名。我一般只用来快速看结构有没有异常变化。
3. 用 snapdiff 内部命令(仅调试版)
Google 内部有一个 snapdiff 工具,不过公开可用的只有 AndroidX 仓库里的 debug 版本。你可以在测试代码里这样调用:
@Test
fun debugSnapFile() {
val snapshot = File("build/snapshots/test.snap")
val content = SnapshotParser.parse(snapshot)
println(content.toJson())
}
嗯,这里 toJson() 不是官方的 API,需要自己加个扩展函数。我做过一个简易版:
fun Snapshot.toJson(): String = buildString {
append("{")
append("\"version\": $version,")
append("\"nodeCount\": ${components.size}")
// 递归展开子节点… 这里省略,怕你照抄后出 bug
append("}")
}
你最好还是用第一种方式,除非你跟我一样喜欢折腾。
理解关键:为什么手动查看很重要?
说白了,快照回归对比失败时,往往是因为 结构差异 而非视觉差异。比如像素差了几个点,但实际是某个 Composable 多了一层 Box,或者 Modifier 顺序变了。如果你只看两张截图,可能发现不了这种“元凶”。
更狠的是——有些坑是 Compose 框架升级带来的隐式变化。我记得有一次 Compose 1.2 -> 1.3,Text 组件的默认行高算法变了,导致所有文本快照全部偏移。那时候我手动查看了十几个 .snap 文件,发现 layoutInfo 里的行高数值集体增加了 1.5dp。要不是我看了结构,还以为是设计师改稿了呢。
你想想看,如果你连 .snap 文件都不看,遇到这种批量回归,就只能一个一个截图比对,效率低到崩溃。
知识结构图:.snap 文件内部层级
快速判断文件是否损坏
有时候 CI 生成的 .snap 文件会是 0KB 或内容不一致。我教你一个土办法:
- 用
cat snapshot.snap | head -c 8 | xxd查看前 8 字节魔数 - 正常的魔数是
0xCCAADD88(小端序显示为88 dd aa cc) - 如果不匹配,说明文件已损坏或来自不同版本
components 列表被截断,根本没法对比。后来我们在管道里加了文件大小校验,才彻底解决。
避坑指南
- 别用文本编辑器打开 .snap——全乱码,浪费时间。
- 手动查看只适合诊断,不要把它当作日常工作流,否则你会像我一样被 PM 追着问“你到底在看什么”。
- 版本对齐:每次更新 Compose 依赖后,记得对比新旧 .snap 文件的
version字段。不一致的话,快照回归对比结果可能全部无效。
嗯,目前为止你应该对 .snap 文件有了一个清晰的认识。它不复杂,但结构细节很关键。能手动看,就能少踩一半的坑。