project(':xxx') 将子模块纳入完整构建生命周期,需在settings.gradle中include声明,支持源码调试、依赖传递和增量编译;files('libs/xxx.jar') 仅加载二进制,无源码、无依赖解析、无自动重编译。

project(':xxx') 和 files('libs/xxx.jar') 的行为完全不同
子模块依赖(project(':xxx'))让 Gradle 把另一个模块当作当前构建的一部分来参与编译、测试和打包;而本地 jar 依赖(files('libs/xxx.jar'))只是把一个已编译好的二进制包“塞进来”,Gradle 不会去读它的源码、不会触发它的编译任务,也不会感知它的内部依赖变化。
settings.gradle 是否需要 include 子模块
使用 project(':xxx') 前,必须在 settings.gradle 中用 include 显式声明该模块路径,否则 Gradle 根本找不到这个 project。而 files() 或 fileTree() 完全不依赖 settings.gradle,只要文件路径存在、拼写正确就能加载。
-
include 'common-utils'缺失 → 报错:Project with path ':common-utils' could not be found in root project -
files('libs/foo.jar')路径写成'lib/foo.jar'(少个 s)→ 编译通过但运行时NoClassDefFoundError -
fileTree(dir: 'libs', include: ['*.jar'])会自动扫描整个目录,适合临时集成多个未发布 jar,但无法控制版本粒度
依赖传递性与编译期可见性差异
project(':xxx') 会完整继承被依赖模块的 api/implementation 声明:如果它用 api 暴露了 gson,那你的模块也能直接用 Gson 类,无需再声明;而 files() 加载的 jar 是“黑盒”,Gradle 不解析其 META-INF/MANIFEST.MF 或 pom,所有它依赖的类都得你手动补全,否则编译失败。
- 子模块里写了
api 'com.google.code.gson:gson:2.10.1'→ 你代码里可以直接 newGson() - 你用
files('libs/common-utils-1.0.jar')→ 即使这个 jar 内部用了 gson,你也得自己加一行implementation 'com.google.code.gson:gson:2.10.1',否则编译报错 -
project()支持断点进源码、跳转定义;files()只能看反编译或手动 attach sources
构建生命周期和增量编译影响
改了子模块的 Java 文件,执行根项目的 ./gradlew build 会自动重新编译那个子模块,并触发依赖它的模块重编译;但改了 libs/xxx.jar 对应的源码,除非你手动重新打包并替换 jar,否则 Gradle 完全无感——它只认文件最后修改时间,不追踪源码变更。
- 子模块启用增量编译后,改一个方法可能只重编译几十毫秒;jar 方式下,哪怕只改一个字节,也得等整个 jar 复制完成才能继续
- CI 流水线里,
project()依赖天然支持构建缓存(build-cache),jar 依赖则容易因路径或时间戳抖动导致缓存失效 - 调试时,子模块日志能准确定位到行号;jar 里没带 debug info 或 source mapping,堆栈全是
Unknown Source
include 了 :network,又 files('libs/network-1.2.jar'),Gradle 不会报错,但 classpath 里会出现两份相同类,运行时行为不可控。


















