约定插件 (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