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


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

    AndroidStudy (Root Project)
    ├─app (Module0)
    ├─CustomView (Module1)
    ├─KotlinCoroutines (Module2)
    ├─...
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    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)
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    里面的大部分配置都是重复的,像 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 中共享的构建逻辑。

    AndroidStudy (Root Project)
    ├─...
    └─build-logic
       |  settings.gradle.kts
       └─ convention
            |  build.gradle.kts
            └─ src
                 └─ main
                      └─ kotlin
                           └─ AndroidLibraryConventionPlugin.kt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
        }
        versionCatalogs {
            create("libs") {
                from(files("../gradle/libs.versions.toml"))
            }
        }
    }
    
    rootProject.name = "build-logic"
    include(":convention")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    build-logic/convention/build.gradle.kts:

    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().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"
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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

    ...
    [plugins]
    bqliang-android-library = { id = "bqliang.android.library", version = "unspecified" }
    ...
    
    • 1
    • 2
    • 3
    • 4

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

    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 {
        override fun apply(target: Project) {
            with(target) {
                with(pluginManager) {
                    // Android Library Module 都需要这 2 个插件
                    apply("com.android.library")
                    apply("org.jetbrains.kotlin.android")
                }
    
                extensions.configure {
                    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().configureEach {
                kotlinOptions {
                    // Set JVM target to 11
                    jvmTarget = JavaVersion.VERSION_11.toString()
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

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

    AndroidStudy/settings.gradle.kts:

    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"
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    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 {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

    Android 学习笔录

    Android 性能优化篇:https://qr18.cn/FVlo89
    Android Framework底层原理篇:https://qr18.cn/AQpN4J
    Android 车载篇:https://qr18.cn/F05ZCM
    Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
    Android 音视频篇:https://qr18.cn/Ei3VPD
    Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
    OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
    Kotlin 篇:https://qr18.cn/CdjtAF
    Gradle 篇:https://qr18.cn/DzrmMB
    Flutter 篇:https://qr18.cn/DIvKma
    Android 八大知识体:https://qr18.cn/CyxarU
    Android 核心笔记:https://qr21.cn/CaZQLo
    Android 往年面试题锦:https://qr18.cn/CKV8OZ
    2023年最新Android 面试题集:https://qr18.cn/CgxrRy
    Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
    音视频面试题锦:https://qr18.cn/AcV6Ap

  • 相关阅读:
    SpringBoot文件上传和下载
    【java期末复习题】第1章 Java 程序设计语言概述
    第二章:jQuery基本操作
    Android学习笔记 2.4.3 实例——使用QuickContactBadge关联联系人 && 2.4.4 实例——可折叠的悬浮按钮
    学会使用这些电脑技巧,可以让你在工作中受益无穷
    第一次汇报yandex广告数据时,应该展示哪些数据
    2018-2019 ACM-ICPC, Asia Nanjing Regional Contest G. Pyramid(组合数学 计数)
    【大学生python】错误和异常
    Jitamin 安装与配置
    C++进阶语法之函数和指针【学习笔记(二)】
  • 原文地址:https://blog.csdn.net/maniuT/article/details/133834122