Gradle Convention Plugin:约定优于配置、共享构建逻辑、版本目录集成、多项目统一配置、实战案例
说实话,Gradle 的脚本写多了,你会发现一个很头疼的问题——每个模块的 build.gradle.kts 里,充斥着大量重复的配置。依赖版本、插件版本、编译选项、签名配置……复制粘贴倒是快,但一旦要改个版本号,那酸爽,谁改谁知道。
Convention Plugin 就是来解决这个痛点的。它把「约定」固化下来,让团队里的每个人都按同一套规则走。说白了,就是「你别再自己瞎配了,用我写好的模板就行」。
什么是 Convention Plugin?
Convention Plugin 本质上是一个自定义 Gradle 插件。但它不是为了扩展 Gradle 功能,而是为了封装「构建约定」。比如:
- 所有 Android 模块都用同一个 compileSdk 版本
- 所有 Kotlin 模块都用同一个语言版本
- 所有模块的 lint 检查规则统一
我个人习惯把 Convention Plugin 放在 build-logic 这个独立模块里。这样主工程和各个业务模块都不需要关心这些配置细节。
核心思想:把「怎么做」封装在插件里,业务模块只关心「做什么」。
版本目录集成:告别魔法数字
版本目录(Version Catalog)是 Gradle 7.0 引入的特性。它把所有的依赖版本号集中管理在一个 libs.versions.toml 文件里。Convention Plugin 和它配合,简直是天作之合。
举个例子,我以前维护一个 20 多个模块的项目,每个模块都写着 implementation "com.squareup.okhttp3:okhttp:4.11.0"。后来升级 OkHttp 到 4.12.0,我花了整整一个下午改版本号。嗯,从那以后我再也不敢手动写版本号了。
版本目录的结构大概是这样:
# gradle/libs.versions.toml
[versions]
agp = "8.2.0"
kotlin = "1.9.20"
okhttp = "4.12.0"
[libraries]
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
然后在 Convention Plugin 里,直接通过 libs 访问:
// build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply(libs.findPlugin("android-application").get().pluginId)
apply(libs.findPlugin("kotlin-android").get().pluginId)
}
android {
compileSdk = 34
defaultConfig {
minSdk = 24
targetSdk = 34
}
}
}
}
}
小技巧:在 Convention Plugin 里访问版本目录,需要先在 build-logic/settings.gradle.kts 里启用版本目录访问器:
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
多项目统一配置:一个插件管所有
多项目架构下,最怕的就是配置不一致。A 模块用 compileSdk 33,B 模块用 34,C 模块忘了升级……这种问题在 Code Review 时很难发现,但编译时就会报错。
Convention Plugin 可以按模块类型分类:
| 插件名称 | 适用模块 | 配置内容 |
|---|---|---|
| android-application-convention | App 模块 | applicationId、签名配置、混淆规则 |
| android-library-convention | Library 模块 | aar 输出、consumerProguardFiles |
| kotlin-library-convention | 纯 Kotlin 模块 | JVM 目标版本、编译器选项 |
| android-feature-convention | 动态功能模块 | distribution、asset pack 配置 |
每个业务模块只需要一行:
// app/build.gradle.kts
plugins {
id("android-application-convention")
}
// core/network/build.gradle.kts
plugins {
id("android-library-convention")
}
你想想看,如果有一天 compileSdk 要升到 35,我只需要改 Convention Plugin 里的一个数字,所有模块自动生效。这不比满世界找版本号香?
实战案例:搭建一个完整的 Convention Plugin
光说不练假把式。我带大家走一遍完整的搭建流程。
第一步:创建 build-logic 模块
在项目根目录下创建 build-logic 文件夹,里面放一个 settings.gradle.kts:
// build-logic/settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")
第二步:编写 Convention Plugin 代码
在 build-logic/convention/src/main/kotlin 下创建插件类:
// AndroidApplicationConventionPlugin.kt
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.run {
pluginManager.apply("com.android.application")
pluginManager.apply("org.jetbrains.kotlin.android")
extensions.configure<ApplicationExtension>("android") {
compileSdk = 34
defaultConfig {
applicationId = "com.example.app"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
}
}
}
第三步:注册插件
在 build-logic/convention/src/main/resources/META-INF/gradle-plugins 下创建属性文件:
# android-application-convention.properties
implementation-class=AndroidApplicationConventionPlugin
第四步:在业务模块中使用
主工程的 settings.gradle.kts 需要引入 build-logic:
pluginManagement {
includeBuild("build-logic")
}
然后业务模块直接引用:
plugins {
id("android-application-convention")
}
注意:我曾经踩过一个坑——Convention Plugin 里硬编码了 applicationId。结果多个 App 模块共用同一个插件时,applicationId 冲突了。解决方案是把可变参数通过扩展函数暴露出来,让业务模块按需覆盖。
知识体系结构图
下面这张图展示了 Convention Plugin 在整个 Gradle 构建体系中的位置和作用:
避坑指南
最后分享几个我实际踩过的坑:
- 插件 ID 冲突:Convention Plugin 的 ID 不要和官方插件重名。我习惯加个公司前缀,比如
com.example.android-application。 - 版本目录路径问题:build-logic 里的
from(files("../gradle/libs.versions.toml"))是相对于 build-logic 目录的。如果项目结构变了,记得调整路径。 - 不要过度封装:有些配置确实需要每个模块独立,比如 applicationId。强行统一反而增加维护成本。我的原则是:80% 的通用配置放插件,20% 的个性化配置留给业务模块自己写。
嗯,Convention Plugin 这东西,用好了能省下大量重复劳动。但也要注意别过度设计。记住一句话:约定优于配置,但约定本身也要灵活可扩展。