常见环境问题:JDK/Gradle版本不兼容导致快照差异
遇到过这么个事儿:本地写好的Compose测试,快照跑得漂漂亮亮,一提交到CI,直接就红了。我当时第一反应是「代码写错了?」可仔细一查,代码完全一样。后来发现——CI用的JDK版本跟我不一样。嗯,快照对比是在比二进制,JDK的微小差异就能让序列化结果、字节码结构发生变化,最终导致测试失败。
说白了,版本不兼容不是Compose的锅,而是构建环境的不一致。这节课我们就来拆一拆这个坑。
问题现象:一模一样的代码,两个结果
最常见的场景是这样的:
- 本地用JDK 11 + Gradle 7.4,快照全部通过
- CI上用JDK 17 + Gradle 8.0,同样的测试,部分快照Assert失败
- 对比两个环境的快照文件,二进制完全相同?不一定,有时是布局偏移几个像素,有时是文本渲染顺序不同
为什么会这样?
Compose快照在截取时依赖系统的Graphics2D渲染管线,而JDK不同版本对字体、抗锯齿、图形变换的实现细节有差异。另外,Gradle的构建缓存策略不一样,可能导致Compile task的输入输出顺序改变,从而影响Compose Compiler输出的字节码顺序。
根源分析:JDK版本差异
我整理过几个核心差异点:
| 差异维度 | 影响范围 | 典型版本 |
|---|---|---|
| 字体指标 | TextLayout测量 | JDK 8→11(迁移到OpenType) |
| 图像渲染 | BufferedImage类型 | JDK 11→17(新增ColorSpace支持) |
| 序列化 | Bitmap序列化结构 | JDK 9+模块化后包路径变化 |
| 字符串编码 | 文件路径/资源名称 | JDK 17+默认使用UTF-8 |
我记得之前有个项目,JDK 11本地快照显示的「确认」按钮,字体是宋体;CI上JDK 17渲染出来变成了微软雅黑——宽了几个像素,整个布局就崩了。所以,快照测试必须跑在同一个JDK上。
根源分析:Gradle版本差异
Gradle版本的影响更隐蔽,因为它不直接参与渲染,但能改变编译顺序和缓存命中:
- Gradle 7.x 和 8.x 的Task输入规范化规则不同
- 增量编译的类文件顺序可能改变
- Compose Compiler Plugin内部使用Gradle API获取目录结构,API变动会导致输出文件hash不一样
一个真实的案例:团队中有人本地用Gradle 7.6,CI用8.2,结果同一个Composable函数生成的@Composable注解的类文件签名差了一个空格,导致快照里的文本对齐偏移了1px。
解决方案:统一版本,锁定环境
方案其实不复杂,核心就三个字:锁版本。下面是我强推的做法:
第一步:统一Gradle版本。用Wrapper锁定,提交到Git。
# 生成wrapper
gradle wrapper --gradle-version 7.6.4
# 或者手动编辑 gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
第二步:统一JDK。用Toolchain声明,这样开发机和CI都下载同一个JDK。
// build.gradle.kts
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.AZUL)
}
}
第三步:CI上强制使用Toolchain指定的JDK,而不是CI默认的JDK。
# GitHub Actions 示例
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
GRADLE_JDK_HOME 或 JAVA_HOME。
最佳实践:一个清单搞定
我建议每个项目建立一份环境兼容性清单:
- 开发环境:JDK 17.0.12,Gradle 7.6.4
- CI环境:JDK 17.0.12(用Toolchain强制覆盖),Gradle 7.6.4(用Wrapper)
- 预提交检查:本地运行一次完整快照测试,再push
- 只读缓存:CI开启Gradle远程缓存(如Build Cache),避免因本地缓存导致差异
下面这张图帮你理清整个链路:
避坑指南
我曾经踩过一个很隐蔽的坑:CI上虽然用了Gradle Wrapper,但Gradle Daemon是用系统默认JDK启动的,导致Toolchain虽然声明了JDK 17,但编译时部分任务混用了JDK 11的类库。解决办法是在CI脚本里显式设置 ORG_GRADLE_PROJECT_javaHome 环境变量。
另外提醒一下:不只是快照测试,所有回归测试的稳定性都依赖环境一致。我建议把JDK和Gradle版本记入项目README,并在CI配置中增加版本检查步骤:
# 检查JDK版本
java -version 2>&1 | grep -i "openjdk version" | awk '{print $3}' | grep -q "17"
if [ $? -ne 0 ]; then
echo "Error: JDK 17 required"
exit 1
fi
- 快照差异 ≠ 代码问题,优先排查环境版本
- 使用Gradle Wrapper锁定Gradle版本
- 使用Java Toolchain锁定JDK版本
- CI上强制覆盖JDK_HOME,避免Daemon污染
- 保持开发/CI/验证环境的Gradle缓存配置一致