第4章 build.gradle 公共配置抽取

4.1 原理:为什么需要公共配置抽取

在多模块 Gradle 项目中,每个子模块的 build.gradle 常常包含大量重复内容:插件声明、仓库地址、依赖版本号、编译选项等。将公共配置抽取到根项目的 build.gradle 中,可显著减少重复代码,统一项目规范,降低升级维护成本。Gradle 提供三种核心机制来实现公共配置抽取:

  • allprojects / subprojects 块 —— 对项目树进行批量配置。
  • 插件 apply 方式 —— 在根项目中统一应用插件并传递到子模块。
  • ext 扩展属性 —— 定义全局变量,供所有子模块引用。

理解它们的作用域和生命周期,是正确抽取配置的前提。

4.2 allprojects vs subprojects 对比

特性 allprojects subprojects
作用范围 当前项目 + 所有子模块 仅子模块(不含根项目)
典型用途 仓库地址、全局插件 classpath 子模块共用的编译配置、依赖声明
配置时机 在 evaluation 之前 在 evaluation 之前
风险 容易污染根项目的配置 不会作用于根项目,更精准

4.3 ext 扩展属性详解

ext 是 Gradle 域对象(如 Project)上的额外属性扩展容器。通过 ext 定义的变量具有全局可见性(在同级域内),常用于保存依赖版本、SDK 路径、环境标记等固定值。

// 根 build.gradle
ext {
    compileSdk = 34
    minSdk = 24
    targetSdk = 34
    libVersion = "1.2.0"
}

// 子模块中直接引用
android {
    compileSdk rootProject.ext.compileSdk
    defaultConfig {
        minSdk rootProject.ext.minSdk
        targetSdk rootProject.ext.targetSdk
    }
}
最佳实践:将 ext 定义集中放在根项目的 build.gradle 或单独 gradle.properties 中,避免在多个子模块中重复定义同名属性导致覆盖。

4.4 实现流程:公共配置抽取四步法

  1. 规划配置分层 —— 区分哪些配置属于所有模块(仓库、插件 classpath),哪些只属于子模块(编译配置、依赖)。
  2. 定义 ext 全局变量 —— 在根 build.gradle 的 buildscript.ext 或 ext 块中声明所有版本号和路径。
  3. 使用 allprojects/subprojects 批量配置 —— 将仓库、公共插件应用放入 allprojects;将编译选项、测试配置放入 subprojects。
  4. 子模块最小化 —— 子模块仅保留模块特有的配置(如 Application 与 Library 差异),通过 apply 插件和引用 rootProject.ext 完成依赖。

完整配置示例(根 build.gradle)

buildscript {
    ext {
        kotlinVersion = "1.9.0"
        agpVersion = "8.2.0"
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$agpVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

subprojects {
    apply plugin: 'com.android.library'

    android {
        compileSdk rootProject.ext.compileSdk
        defaultConfig {
            minSdk rootProject.ext.minSdk
        }
    }

    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
    }
}

4.5 调试与常见错误

  • ext 属性未定义:子模块引用 rootProject.ext.compileSdk 时,若根项目未在 evaluation 前定义 ext,会抛出 MissingPropertyException。确保 ext 块放在 buildscript 外部或在 buildscript 内部但通过 ext.kotlinVersion 定义。
  • allprojects 中配置冲突:根项目本身也可能应用某些插件,若在 allprojects 中重复 apply,会导致配置二义性。使用 subprojects 避免影响根项目。
  • 属性覆盖:子模块中重新定义同名 ext 属性会覆盖根项目值,导致依赖版本不一致。应在子模块中优先引用 rootProject.ext.*,而非重新赋值。
注意:apply plugin 在 subprojects 块中应用到所有子模块,若某些子模块不是 Android Library(如纯 Java 模块),还需通过条件判断(if (project.name != 'utils'))排除,否则会因缺少 Android 配置而失败。

4.6 性能与稳定性优化

  • 避免在 allprojects/subprojects 块中执行耗时计算(如读取文件、网络请求),因为每次构建都会执行一次。可将计算结果缓存到 ext 属性中。
  • 使用 gradle.properties 存储系统级配置(如 JVM 参数),与 ext 互补,减少 build.gradle 的解析复杂度。
  • 将不变配置(如仓库地址)抽入 settings.gradle 中的 pluginManagement 和 dependencyResolutionManagement,进一步分离职责。

4.7 公共配置抽取结构图

Gradle 公共配置抽取结构 根 build.gradle allprojects 对所有模块批量配置 subprojects 仅作用于子模块 ext 扩展属性 定义全局版本/路径 :app 模块 :core 模块 :lib 模块 :util 模块 ext 变量引用 配置流:定义 ext → 通过 allprojects/subprojects 分发 → 子模块引用 降低重复,统一版本,提升构建可维护性