解决 Android/Gradle 编译报错:Comparison method violates its general contract!

17 阅读3分钟

踩坑背景

在多人协作开发 React Native / Android 项目时,经常会遇到这种“玄学”问题:我的电脑上编译运行一切正常,但同事拉取代码后运行 ./gradlew clean 或者 yarn android 却直接报错:

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

修改前:

org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

修改后:

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 目录,执行以下命令:

# Windows
.\gradlew.bat --stop

# Mac/Linux
./gradlew --stop

第三步:重新编译

守护进程停止后,再次运行构建命令,Gradle 会重新拉起一个新的 Daemon,此时新的 JVM 参数就会生效:

# 清理并重新构建
./gradlew clean

问题迎刃而解!🎉

经验总结

  1. 统一环境很重要:团队协作时,如果经常出现类似问题,最好统一大家的 JDK 版本(例如统一使用 JDK 11 或 17)。
  2. 理解 Gradle Daemon 的机制:凡是涉及到修改 Gradle 内存(Xmx)、JVM 参数(jvmargs)等全局配置,改完一定要记得 ./gradlew --stop 重启 Daemon,否则你会被假象迷惑半天。
  3. 排错先排环境:“我行他不行”的问题,99% 都是由于 Node 版本、JDK 版本、环境变量或缓存(Daemon/Watchman 等)不一致导致的。