踩坑背景
在多人协作开发 React Native / Android 项目时,经常会遇到这种"玄学"问题:我的电脑上编译运行一切正常,但同事拉取代码后运行 ./gradlew clean 或者 yarn android 却直接报错:
text
FAILURE: Build failed with an exception.
* Where:
Build file 'D:\Project\...\android\app\build.gradle' line: 80
* What went wrong:
A problem occurred evaluating project ':app'.
> Comparison method violates its general contract!
问题原因
这个错误并不是代码写错了,而是 JDK 版本差异与底层排序算法 引起的。
从 Java 7 开始,Collections.sort() 和 Arrays.sort() 底层默认切换到了更严格的 TimSort 算法。这个算法对比较器(Comparator)的规约要求非常严格(比如必须满足传递性:如果 A > B,B > C,则必须保证 A > C)。 如果在项目依赖的某个 Gradle 插件(或构建脚本内部)使用了一个不太严谨的排序实现,在较新或配置更严格的 JDK 版本下,就会因为校验失败抛出 Comparison method violates its general contract! 异常。
这就导致了:你电脑上的 JDK 环境刚好没有触发这套严格校验(或者版本较低),而你同事的 JDK 版本较新,导致编译直接挂掉。
解决思路
既然是新版排序算法校验太严格,我们可以通过配置 JVM 参数,强制让 Java 回退到旧版相对宽松的合并排序算法(Legacy Merge Sort)。
具体操作步骤
第一步:修改 gradle.properties
找到项目下的 android/gradle.properties 文件,在 org.gradle.jvmargs 这一行后面追加参数 -Djava.util.Arrays.useLegacyMergeSort=true。
修改前:
properties
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
修改后:
properties
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -Djava.util.Arrays.useLegacyMergeSort=true
注意: 不能把这个参数写在
local.properties里,因为 Gradle 在启动守护进程(Daemon)时只会读取gradle.properties,并不会去解析local.properties里的jvmargs。
第二步:杀死后台 Gradle 守护进程(关键大坑!)
这是最容易踩坑的一步! 很多开发者修改了 gradle.properties 后再次运行编译,发现错误依然存在,就开始怀疑人生。
原因在于:Gradle 守护进程(Daemon)是在后台常驻的,它启动时就已经读取并固化了旧的 JVM 参数。你修改了文件,但正在运行的 Daemon 并不会热更新这些参数!
必须手动停掉旧的 Daemon: 打开终端,进入 android 目录,执行以下命令:
bash
# Windows
.\gradlew.bat --stop
# Mac/Linux
./gradlew --stop
第三步:重新编译
守护进程停止后,再次运行构建命令,Gradle 会重新拉起一个新的 Daemon,此时新的 JVM 参数就会生效:
bash
# 清理并重新构建
./gradlew clean
问题迎刃而解!🎉
经验总结
- 统一环境很重要:团队协作时,如果经常出现类似问题,最好统一大家的 JDK 版本(例如统一使用 JDK 11 或 17)。
- 理解 Gradle Daemon 的机制 :凡是涉及到修改 Gradle 内存(
Xmx)、JVM 参数(jvmargs)等全局配置,改完一定要记得./gradlew --stop重启 Daemon,否则你会被假象迷惑半天。 - 排错先排环境:"我行他不行"的问题,99% 都是由于 Node 版本、JDK 版本、环境变量或缓存(Daemon/Watchman 等)不一致导致的。