约定插件 (Convention Plugins)
源:Gradle 官方文档 - Sharing Build Logic
约定插件是现代 Gradle 推荐的构建逻辑复用方案,解决多模块项目中重复配置的问题。
为什么需要约定插件
传统方式的问题
重复配置:
kotlin
// module-a/build.gradle.kts
android {
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
// module-b/build.gradle.kts
android {
compileSdk = 34 // 重复!
defaultConfig {
minSdk = 24 // 重复!
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 // 重复!
targetCompatibility = JavaVersion.VERSION_17 // 重复!
}
}allprojects/subprojects 的问题:
kotlin
// ❌ 不推荐
allprojects {
// 所有模块都受影响,包括不需要的
}
subprojects {
// 难以维护,强耦合
}问题:
- 全局配置污染
- 难以按需应用
- 破坏构建缓存
- 类型安全缺失
约定插件的优势
kotlin
// ✅ 推荐:约定插件
plugins {
id("my.android.library") // 一行搞定
}优势:
- 按需应用
- 类型安全
- 代码复用
- 独立缓存
约定插件概念
什么是约定插件
约定插件:将通用的构建配置封装成插件,各模块按需引用。
核心思想:
- 定义"约定"(Convention)
- 封装成插件
- 模块按需应用
与普通插件的区别
| 特性 | 普通插件 | 约定插件 |
|---|---|---|
| 用途 | 通用功能 | 项目特定配置 |
| 发布 | Maven/Plugin Portal | 项目内部 |
| 示例 | Android Gradle Plugin | 项目的 Library 配置 |
buildSrc vs 约定插件
buildSrc 方案
结构:
project/
├── buildSrc/
│ ├── build.gradle.kts
│ └── src/main/kotlin/
│ └── AndroidLibraryPlugin.kt
├── app/
└── core/优点:
- 简单直接
- 自动识别
缺点:
- ❌ 修改 buildSrc 导致全项目缓存失效
- ❌ 难以跨项目共享
- ❌ 构建性能差
约定插件方案(推荐)
结构:
project/
├── build-logic/
│ ├── convention/
│ │ ├── build.gradle.kts
│ │ └── src/main/kotlin/
│ │ └── AndroidLibraryConventionPlugin.kt
│ └── settings.gradle.kts
├── app/
└── core/优点:
- ✅ 独立构建缓存
- ✅ 可跨项目共享
- ✅ 更好的隔离
- ✅ IDE 支持更好
创建约定插件
第一步:创建 build-logic 项目
项目结构:
build-logic/
├── convention/
│ ├── build.gradle.kts
│ └── src/main/kotlin/
│ └── AndroidLibraryConventionPlugin.kt
└── settings.gradle.ktsbuild-logic/settings.gradle.kts:
kotlin
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")build-logic/convention/build.gradle.kts:
kotlin
plugins {
`kotlin-dsl`
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}第二步:编写插件
AndroidLibraryConventionPlugin.kt:
kotlin
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
// 应用必要的插件
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
}
// 配置 Android
extensions.configure<LibraryExtension> {
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
}
}
}第三步:注册插件
build-logic/convention/build.gradle.kts:
kotlin
gradlePlugin {
plugins {
register("androidLibrary") {
id = "my.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
}
}第四步:引入 build-logic
主项目 settings.gradle.kts:
kotlin
pluginManagement {
includeBuild("build-logic")
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyApp"
include(":app")
include(":core")第五步:使用插件
core/build.gradle.kts:
kotlin
plugins {
id("my.android.library") // 使用约定插件
}
dependencies {
// 只写业务特定的依赖
implementation(libs.androidx.core.ktx)
}访问 Version Catalog
在插件中读取 libs
扩展函数:
kotlin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
internal val Project.libs
get(): VersionCatalog =
extensions.getByType<VersionCatalogsExtension>().named("libs")使用:
kotlin
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.configure<LibraryExtension> {
val compileSdkVersion = libs.findVersion("compileSdk")
.get().requiredVersion.toInt()
compileSdk = compileSdkVersion
}
}
}
}多种约定插件
Android Library 插件
kotlin
// AndroidLibraryConventionPlugin.kt
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
}
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
}
}
}
}Android Application 插件
kotlin
// AndroidApplicationConventionPlugin.kt
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.application")
apply("org.jetbrains.kotlin.android")
}
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
}
}
}
}Compose 插件
kotlin
// AndroidComposeConventionPlugin.kt
class AndroidComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.configure<CommonExtension<*, *, *, *, *, *>> {
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs
.findVersion("androidxComposeCompiler")
.get().requiredVersion
}
dependencies {
val bom = libs.findLibrary("androidx-compose-bom").get()
add("implementation", platform(bom))
add("implementation", libs.findLibrary("androidx-compose-ui").get())
add("implementation", libs.findLibrary("androidx-compose-material3").get())
}
}
}
}
}提取通用配置
Kotlin Android 配置
kotlin
// KotlinAndroid.kt
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *, *>
) {
commonExtension.apply {
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
// Kotlin 编译选项
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
}实战案例
案例1:完整的插件体系
插件列表:
kotlin
// build-logic/convention/build.gradle.kts
gradlePlugin {
plugins {
register("androidApplication") {
id = "my.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidLibrary") {
id = "my.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
register("androidCompose") {
id = "my.android.compose"
implementationClass = "AndroidComposeConventionPlugin"
}
register("androidHilt") {
id = "my.android.hilt"
implementationClass = "AndroidHiltConventionPlugin"
}
}
}使用:
kotlin
// app/build.gradle.kts
plugins {
id("my.android.application")
id("my.android.compose")
id("my.android.hilt")
}
// feature-login/build.gradle.kts
plugins {
id("my.android.library")
id("my.android.compose")
}
// core/build.gradle.kts
plugins {
id("my.android.library")
}案例2:Now in Android 模式
参考 Google 官方项目:
build-logic/
├── convention/
│ └── src/main/kotlin/
│ ├── AndroidApplicationConventionPlugin.kt
│ ├── AndroidLibraryConventionPlugin.kt
│ ├── AndroidFeatureConventionPlugin.kt
│ ├── AndroidComposeConventionPlugin.kt
│ ├── AndroidHiltConventionPlugin.kt
│ ├── AndroidRoomConventionPlugin.kt
│ └── KotlinAndroid.kt最佳实践
插件命名:
kotlin
id = "my.android.library" // 使用项目前缀
id = "my.android.compose"
id = "my.android.hilt"提取通用逻辑:
kotlin
// 创建 KotlinAndroid.kt、AndroidCompose.kt 等
configureKotlinAndroid(commonExtension)
configureAndroidCompose(commonExtension)Version Catalog 访问:
kotlin
internal val Project.libs
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")插件组合:
kotlin
plugins {
id("my.android.library")
id("my.android.compose") // 可组合使用
}文件组织:
convention/src/main/kotlin/
├── AndroidApplicationConventionPlugin.kt
├── AndroidLibraryConventionPlugin.kt
├── AndroidComposeConventionPlugin.kt
└── utils/
├── KotlinAndroid.kt
├── AndroidCompose.kt
└── ProjectExtensions.kt