约定插件深度实践
源:Now in Android | Gradle 官方文档
深入探讨如何构建工业级的约定插件体系,实现全项目构建逻辑的高度内聚与复用。
插件体系架构设计
分层设计理念
推荐的四层架构(参考 Now in Android):
build-logic/convention/
├── AndroidApplicationConventionPlugin.kt # 应用层
├── AndroidLibraryConventionPlugin.kt # 库层
├── AndroidFeatureConventionPlugin.kt # 功能层
├── AndroidComposeConventionPlugin.kt # Compose 层
├── AndroidHiltConventionPlugin.kt # Hilt 层
├── AndroidRoomConventionPlugin.kt # Room 层
└── utils/
├── KotlinAndroid.kt # 通用配置
├── AndroidCompose.kt
└── ProjectExtensions.kt层级关系:
基础层:配置 Kotlin + Android 基础
↓
核心层:区分 Application / Library
↓
功能层:Compose / Hilt / Room
↓
应用层:Feature 模块组合插件职责划分
| 插件类型 | 职责 | 示例 |
|---|---|---|
| 基础插件 | Kotlin/Java 配置 | kotlinOptions, jvmTarget |
| 平台插件 | Android 通用配置 | compileSdk, minSdk |
| 类型插件 | Application/Library | applicationId, consumerProguardFiles |
| 功能插件 | 特定功能 | Compose, Hilt, Room |
可配置扩展
为什么需要扩展
问题:硬编码的插件不够灵活。
kotlin
// ❌ 硬编码
class ComposePlugin : Plugin<Project> {
override fun apply(target: Project) {
// Compose 始终启用
buildFeatures.compose = true
}
}解决方案:提供配置扩展。
kotlin
// ✅ 可配置
class ComposePlugin : Plugin<Project> {
override fun apply(target: Project) {
val extension = target.extensions.create<ComposeExtension>("composeConfig")
if (extension.enabled.getOrElse(true)) {
buildFeatures.compose = true
}
}
}定义扩展
kotlin
// ComposeExtension.kt
interface ComposeExtension {
val enabled: Property<Boolean>
val kotlinCompilerVersion: Property<String>
}使用扩展
kotlin
class AndroidComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
// 创建扩展
val extension = extensions.create<ComposeExtension>("composeConfig")
extensions.configure<CommonExtension<*, *, *, *, *, *>> {
buildFeatures {
compose = extension.enabled.getOrElse(true)
}
composeOptions {
kotlinCompilerExtensionVersion = extension.kotlinCompilerVersion
.getOrElse(libs.findVersion("androidxComposeCompiler").get().requiredVersion)
}
}
}
}
}调用方使用:
kotlin
// feature/login/build.gradle.kts
plugins {
id("my.android.compose")
}
composeConfig {
enabled.set(true)
kotlinCompilerVersion.set("1.5.10")
}插件组合模式
基础插件 + 功能插件
设计思路:基础插件提供平台配置,功能插件添加特定能力。
AndroidLibraryConventionPlugin(基础):
kotlin
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)
}
}
}
}AndroidFeatureConventionPlugin(组合):
kotlin
class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
// 应用基础插件
pluginManager.apply {
apply("my.android.library")
apply("my.android.compose")
apply("my.android.hilt")
}
// Feature 特定配置
extensions.configure<LibraryExtension> {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
// Feature 通用依赖
dependencies {
add("implementation", libs.findLibrary("androidx.lifecycle.viewmodel").get())
add("implementation", libs.findLibrary("androidx.navigation.compose").get())
}
}
}
}使用:
kotlin
// 只需一行
plugins {
id("my.android.feature")
}访问 Version Catalog
扩展函数方式
kotlin
// ProjectExtensions.kt
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")
// 获取版本
internal fun VersionCatalog.version(alias: String): String =
findVersion(alias).get().requiredVersion
// 获取库
internal fun VersionCatalog.library(alias: String): Provider<MinimalExternalModuleDependency> =
findLibrary(alias).get()在插件中使用
kotlin
class AndroidComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.configure<CommonExtension<*, *, *, *, *, *>> {
// 使用 libs
val composeCompilerVersion = libs.version("androidxComposeCompiler")
composeOptions {
kotlinCompilerExtensionVersion = composeCompilerVersion
}
dependencies {
val bom = libs.library("androidx-compose-bom")
add("implementation", platform(bom))
}
}
}
}
}统一依赖管理
在插件中添加依赖
kotlin
class AndroidHiltConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.dagger.hilt.android")
apply("com.google.devtools.ksp")
}
// 统一添加 Hilt 依赖
dependencies {
add("implementation", libs.library("hilt.android"))
add("ksp", libs.library("hilt.compiler"))
// 测试依赖
add("testImplementation", libs.library("hilt.android.testing"))
add("kspTest", libs.library("hilt.compiler"))
}
}
}
}批量依赖组
kotlin
class TestingConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
dependencies {
// 单元测试全家桶
add("testImplementation", libs.library("junit"))
add("testImplementation", libs.library("kotlin.test"))
add("testImplementation", libs.library("kotlinx.coroutines.test"))
add("testImplementation", libs.library("turbine"))
// Android 测试全家桶
add("androidTestImplementation", libs.library("androidx.test.ext.junit"))
add("androidTestImplementation", libs.library("androidx.test.espresso.core"))
add("androidTestImplementation", libs.library("androidx.compose.ui.test"))
}
}
}
}动态配置
基于 gradle.properties
kotlin
class FeatureFlagPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.configure<ApplicationExtension> {
defaultConfig {
// 从 gradle.properties 读取
val enableLogging = findProperty("feature.logging")?.toString()?.toBoolean() ?: false
buildConfigField("Boolean", "ENABLE_LOGGING", "$enableLogging")
val apiUrl = findProperty("api.url")?.toString() ?: "https://api.example.com"
buildConfigField("String", "API_URL", "\"$apiUrl\"")
}
}
}
}
}gradle.properties:
properties
feature.logging=true
api.url=https://staging.example.com基于环境变量
kotlin
class EnvironmentPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
val buildEnv = providers.environmentVariable("BUILD_ENV").getOrElse("dev")
extensions.configure<ApplicationExtension> {
buildTypes {
getByName("release") {
// 根据环境选择配置
when (buildEnv) {
"prod" -> {
applicationIdSuffix = null
debuggable = false
}
"staging" -> {
applicationIdSuffix = ".staging"
debuggable = true
}
else -> {
applicationIdSuffix = ".dev"
debuggable = true
}
}
}
}
}
}
}
}实战案例
案例1:Now in Android 插件体系
完整的插件列表:
kotlin
// build-logic/convention/build.gradle.kts
gradlePlugin {
plugins {
register("androidApplicationCompose") {
id = "nowinandroid.android.application.compose"
implementationClass = "AndroidApplicationComposeConventionPlugin"
}
register("androidApplication") {
id = "nowinandroid.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidLibraryCompose") {
id = "nowinandroid.android.library.compose"
implementationClass = "AndroidLibraryComposeConventionPlugin"
}
register("androidLibrary") {
id = "nowinandroid.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
register("androidFeature") {
id = "nowinandroid.android.feature"
implementationClass = "AndroidFeatureConventionPlugin"
}
register("androidHilt") {
id = "nowinandroid.android.hilt"
implementationClass = "AndroidHiltConventionPlugin"
}
register("androidRoom") {
id = "nowinandroid.android.room"
implementationClass = "AndroidRoomConventionPlugin"
}
}
}案例2:多环境配置插件
kotlin
class MultiEnvPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.configure<ApplicationExtension> {
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
buildConfigField("String", "API_URL", "\"https://dev-api.example.com\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField("String", "API_URL", "\"https://staging-api.example.com\"")
}
create("prod") {
dimension = "environment"
buildConfigField("String", "API_URL", "\"https://api.example.com\"")
}
}
}
}
}
}最佳实践
插件命名规范:
<项目名>.<平台>.<类型>[.<功能>]
nowinandroid.android.application
nowinandroid.android.library.compose
nowinandroid.android.feature文件组织:
convention/src/main/kotlin/
├── AndroidApplicationConventionPlugin.kt
├── AndroidLibraryConventionPlugin.kt
├── AndroidFeatureConventionPlugin.kt
├── AndroidComposeConventionPlugin.kt
├── AndroidHiltConventionPlugin.kt
└── utils/
├── KotlinAndroid.kt
├── AndroidCompose.kt
└── ProjectExtensions.kt插件组合:
kotlin
// 基础插件
id("my.android.library")
// 功能插件(可选)
id("my.android.compose")
id("my.android.hilt")
// 或使用组合插件
id("my.android.feature") // 包含上述所有提取通用配置:
kotlin
// 不要在每个插件中重复写
internal fun Project.configureKotlinAndroid(...)
internal fun Project.configureAndroidCompose(...)使用 Provider API:
kotlin
val version = providers.environmentVariable("VERSION").getOrElse("1.0.0")版本集中管理:
kotlin
// 从 Version Catalog 读取
val compileSdk = libs.version("compileSdk").toInt()