Skip to content

约定插件 (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")  // 一行搞定
}

优势

  • 按需应用
  • 类型安全
  • 代码复用
  • 独立缓存

约定插件概念

什么是约定插件

约定插件:将通用的构建配置封装成插件,各模块按需引用。

核心思想

  1. 定义"约定"(Convention)
  2. 封装成插件
  3. 模块按需应用

与普通插件的区别

特性普通插件约定插件
用途通用功能项目特定配置
发布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.kts

build-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