如何共享 Android 不同模块的构建配置

Gradle Convention Plugin

最近想重新梳理学习一遍 Android 的各个知识点,于是新建了一个 AndroidStudy 项目仓库,打算每个知识块新建 1 个 module。

类似这样:

scss 复制代码
AndroidStudy (Root Project)
├─app (Module0)
├─CustomView (Module1)
├─KotlinCoroutines (Module2)
├─...

然后发现每新建 1 个 Android Library Module 都生成 1 个新的 build.gradle.kts 文件

kotlin 复制代码
plugins {
    alias(libs.plugins.com.android.library)
    alias(libs.plugins.org.jetbrains.kotlin.android)
}

android {
    namespace = "com.bqliang.mylibrary"
    compileSdk = 33

    defaultConfig {
        minSdk = 26

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    implementation(libs.core.ktx)
    ...
}

里面的大部分配置都是重复的,像 compileSdk、minSdk、compileOptions、kotlinOptions... 虽然我以后也不会再去改这些配置了,但是代码洁癖还是让我想把这些重复的配置去掉,经过一番搜索,发现 Gradle Convention Plugin 非常适合解决这个问题。

什么是 Gradle Convention Plugin

为了解决上面的问题,我们自然很容易想到要把其中可以共享的配置抽取出来,然后在每个 module 中引用这些配置。我们可以编写 1 个预编译插件 AndroidLibraryPlugin,在其中去处理这些共享的构建逻辑,然后在需要的 module 中引用这个插件。这样就可以避免重复的配置了,这样的插件就叫做 Gradle Convention Plugin,所以 Gradle Convention Plugin 并不是指某个具体的插件(像 com.android.library),而是指一类插件,这类插件的作用就是抽取出一些共享的构建逻辑。

如何编写 Gradle Convention Plugin

提到预编译插件 ,很多人知道可以写在 buildSrc 目录下,但是这种方式还是有一些缺点的,比如任何更改都会导致整个项目重新编译,更好的方式是使用复合构建(composite build)

我们新建 1 个 build-logic 项目,文件结构如下,我们将在 AndroidLibraryConventionPlugin 中编写我们的要在 Android Library Module 中共享的构建逻辑。

css 复制代码
AndroidStudy (Root Project)
├─...
└─build-logic
   |  settings.gradle.kts
   └─ convention
        |  build.gradle.kts
        └─ src
             └─ main
                  └─ kotlin
                       └─ AndroidLibraryConventionPlugin.kt

因为复合构建项目里的构建是完全独立的,所以我们需要在 build-logic/settings.gradle.kts 中声明依赖仓库源,也要显式声明 versionCatalogs

AndroidStudy/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 复制代码
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    `kotlin-dsl`
}

group = "com.bqliang.gradleconventionplugins.buildlogic"

// Configure the build-logic plugins to target JDK 17
// This matches the JDK used to build the project, and is not related to what is running on device.
java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }
}

dependencies {
    compileOnly(libs.android.gradlePlugin)
    compileOnly(libs.kotlin.gradlePlugin)
}

gradlePlugin {
    // register the convention plugin
    plugins {
        register("androidLibrary") {
            id = "bqliang.android.library"
            implementationClass = "AndroidLibraryConventionPlugin"
        }
    }
}

这里的 sourceCompatibility、targetCompatibility、kotlinOptions 只是用来指定编译我们的 Gradle Convention Plugin 的 JDK 版本,和共享的构建逻辑没有关系。文件最后注册了我们的 Gradle Convention Plugin,为了后续方便引用,我们可以把 plugin id 写在 libs.versions.toml 里:

toml 复制代码
...
[plugins]
bqliang-android-library = { id = "bqliang.android.library", version = "unspecified" }
...

build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt:

kotlin 复制代码
import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.LibraryExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

class AndroidLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            with(pluginManager) {
                // Android Library Module 都需要这 2 个插件
                apply("com.android.library")
                apply("org.jetbrains.kotlin.android")
            }

            extensions.configure<LibraryExtension> {
                configureKotlinAndroid(this)
                defaultConfig.targetSdk = 34
            }
        }
    }

    private fun Project.configureKotlinAndroid(
        commonExtension: CommonExtension<*, *, *, *, *>,
    ) {
        commonExtension.apply {
            compileSdk = 34

            defaultConfig {
                minSdk = 26
            }

            compileOptions {
                sourceCompatibility = JavaVersion.VERSION_11
                targetCompatibility = JavaVersion.VERSION_11
            }
        }

        configureKotlin()
    }

    private fun Project.configureKotlin() {
        // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947
        tasks.withType<KotlinCompile>().configureEach {
            kotlinOptions {
                // Set JVM target to 11
                jvmTarget = JavaVersion.VERSION_11.toString()
            }
        }
    }
}

注意,目前我们还没有在 root project 中 include 这个 build-logic 项目呢,因为复合构建会把项目里的构建配置包含进来,所以我们不能直接使用 include(":build-logic"),而是要使用 includeBuild("build-logic")

AndroidStudy/settings.gradle.kts:

kotlin 复制代码
pluginManagement {
    includeBuild("build-logic") // include build-logic module
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "AndroidStudy"
...

现在我们就可以使用我们的预编译约定插件 AndroidLibraryConventionPlugin 了,回到文章一开始那个 Android Library Module 的 build.gradle.kts,我们可以把里面的大部分配置都去掉了,只是简单的引用一下我们的插件就可以了。

kotlin 复制代码
plugins {
    alias(libs.plugins.bqliang.android.library) // apply our convention plugin
}

android {
    namespace = "com.bqliang.mylibrary"

    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

dependencies {
    ...
}

当然这里只是把 Android Library Module 的构建逻辑抽取出来了,其实像 Application Module、Jetpack Compose、Room 等等构建逻辑都是可以抽取出来的。

参考资料:
Now in Android - build-logic
Gradle Composing builds
Sharing build logic between subprojects Sample

相关推荐
松仔log2 小时前
Java中级——组合和继承
android·java·开发语言
我命由我123455 小时前
Jetpack Compose - MaterialExpressiveTheme 与 MaterialTheme、ColorScheme
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
lilian2336 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
2601_955759627 小时前
ClaudeAPI成本中心与业务标签设计指南
android·java·数据库
余衫马7 小时前
2026最新版!Android Studio 极速安装与配置指南
android·ide·android studio
AFinalStone7 小时前
Android 7系统异常问题排查(五)Framework层(下)—System Server崩溃
android·tombstone·系统异常
AFinalStone7 小时前
Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断
android·系统异常
极客猴子11 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker11 小时前
Boolean 变量到底该怎么命名?
android·kotlin
AFinalStone11 小时前
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解
android·系统异常