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 构建体系中的位置和作用:

Gradle Convention Plugin 知识体系 版本目录 (libs.versions.toml) Convention Plugin 约定优于配置 · 共享构建逻辑 App 模块 applicationId, 签名, 混淆 Library 模块 aar, consumerProguard Feature 模块 动态功能, asset pack 统一配置:compileSdk=34, minSdk=24, targetSdk=34 一处修改,全局生效

避坑指南

最后分享几个我实际踩过的坑:

  • 插件 ID 冲突:Convention Plugin 的 ID 不要和官方插件重名。我习惯加个公司前缀,比如 com.example.android-application
  • 版本目录路径问题:build-logic 里的 from(files("../gradle/libs.versions.toml")) 是相对于 build-logic 目录的。如果项目结构变了,记得调整路径。
  • 不要过度封装:有些配置确实需要每个模块独立,比如 applicationId。强行统一反而增加维护成本。我的原则是:80% 的通用配置放插件,20% 的个性化配置留给业务模块自己写。

嗯,Convention Plugin 这东西,用好了能省下大量重复劳动。但也要注意别过度设计。记住一句话:约定优于配置,但约定本身也要灵活可扩展。

公众号:蓝海资料掘金营,微信deep3321