项目级build.gradle配置:allprojects、subprojects、ext扩展属性
模块化项目里,根目录下的build.gradle就像指挥官。它统筹着所有子模块的编译配置。我见过不少团队把依赖版本写死在各个子模块里,结果升级时要改几十个文件……嗯,今天就来聊聊怎么用好allprojects、subprojects和ext,让配置既清晰又好维护。
allprojects与subprojects——全局与子模块的配置边界
这两个块看起来很像,但作用范围有本质区别。我用一个表格来对比,你一看就明白:
| 块 | 作用对象 | 典型用途 |
|---|---|---|
allprojects {} |
根项目 + 所有子模块 | 全局仓库地址、通用插件、全局变量 |
subprojects {} |
仅子模块 | 模块公共配置(如Java版本、lint选项) |
看明白了吧?allprojects连根项目自己都包括,而subprojects只针对子模块。我建议仓库声明放在allprojects里,模块通用的编译配置放在subprojects里。举个实际例子:
// 项目级 build.gradle
allprojects {
repositories {
google()
mavenCentral()
}
}
subprojects {
// 每个子模块都应用 common.gradle 插件约定
afterEvaluate { project ->
if (project.plugins.hasPlugin('com.android.application') ||
project.plugins.hasPlugin('com.android.library')) {
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
}
}
}
}
}
我在项目里曾遇到一个坑:有人在subprojects里配置了apply plugin: 'kotlin-android',结果导致某个纯Java模块也被强制应用了Kotlin插件,编译时报了一堆class冲突……所以我后来改用按条件判断,就像上面代码里那样——先检查插件再配置。原则是:不要无脑套用,要区分模块类型。
ext扩展属性——统一版本管理的神器
说到版本管理,ext是模块化项目的救星。你想想,几十个模块各自维护targetSdk、versionName,那画面太美我不敢看。我习惯在项目根build.gradle底部定义一个ext块,把所有公共版本号收拢起来:
ext {
// 版本号
compileSdkVersion = 34
minSdkVersion = 21
targetSdkVersion = 34
// 依赖版本
kotlinVersion = '1.9.22'
composeBomVersion = '2024.02.00'
retrofitVersion = '2.9.0'
okhttpVersion = '4.12.0'
// 依赖简写(避免到处拷贝坐标)
libs = [
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion",
composeUi : "androidx.compose.ui:ui:1.6.0",
retrofit : "com.squareup.retrofit2:retrofit:$retrofitVersion",
okhttp : "com.squareup.okhttp3:okhttp:$okhttpVersion"
]
}
小技巧:用ext.libs这种嵌套结构来定义完整的依赖坐标,子模块引用时只需写implementation rootProject.ext.libs.retrofit,既统一又简洁。不过要注意:ext里的变量是动态的,在buildscript块里无法直接使用——如果要在buildscript里引用版本号,需要把版本号定义在项目根目录的gradle.properties里。
配置优先级与执行顺序
这三个块在同一个build.gradle里执行顺序很关键:先执行buildscript {}和插件声明,然后按文件书写顺序执行allprojects、subprojects、ext。但ext其实是在allprojects之前就被解析的?我亲自验证过:ext块会在配置阶段就赋值,所以allprojects里能直接读取ext定义的变量。而subprojects里也能读取,但要注意闭包的延迟求值问题。
我曾经遇到一个诡异的bug:在subprojects里写println ext.kotlinVersion,发现输出的是null。后来才意识到是闭包捕获了变量引用,而当时ext还没赋值完毕。解决方案就是不要在subprojects的顶层闭包内直接引用ext变量,而是要放在afterEvaluate里,或者使用rootProject.ext.kotlinVersion显式引用——你可以试试看。
⚠ 特别注意:不要在allprojects里修改子模块的android块(如compileSdkVersion)!我见过有人想省事,在allprojects里统一设置android.compileSdkVersion = 34,结果某些模块的android块还没创建,直接报了MissingPropertyException。正确的做法是在subprojects里用afterEvaluate包装,或者让子模块显式继承一个公共gradle脚本。
整体关系图
下面这张图帮你理清allprojects、subprojects、ext在项目级配置中的角色和关系:
你看,ext像是一个全局常量池,allprojects和subprojects都能直接食用它。而allprojects负责把通用仓库声明透传给所有项目,subprojects则专注给子模块叠加公共编译配置。这三者配合好了,模块化项目的配置就不再是“一个改、处处改”的噩梦。我自己现在的模板项目,只要改一行ext里的版本号,整个项目的依赖就能自动同步,舒心得很。