如何共享 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

相关推荐
雨白3 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk3 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING4 小时前
RN容器启动优化实践
android·react native
恋猫de小郭6 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker11 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴12 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos