5月27日 23:32

Gradle 如何实现构建变体和多环境配置?

构建变体是 Android Gradle 构建系统的核心机制,面试中经常围绕 buildTypes、productFlavors、flavorDimensions 和 sourceSet 合并优先级展开。理解它的关键在于:构建变体 = 构建类型 × 产品风味,是多维度组合的结果。

构建变体是什么

构建变体(Build Variant)是 buildTypes 和 productFlavors 的交叉组合产物。每增加一个构建类型或产品风味,变体数量都会成倍增长。假设配置了 2 个 buildTypes 和 3 个 productFlavors,最终会生成 2 × 3 = 6 个构建变体。

变体的命名规则是:产品风味名 + 构建类型名(驼峰拼接),例如 freeDebugpaidRelease

buildTypes 和 productFlavors 各负责什么

buildTypes 定义构建的行为特征——是否混淆、是否可调试、签名配置等。它回答"这个包怎么构建"。

productFlavors 定义产品的业务差异——包名、应用名、API 地址、功能模块等。它回答"这个包是什么版本"。

两者职责不同,组合使用才能覆盖"多版本 × 多环境"的完整需求。

flavorDimensions 的作用和顺序

当存在多个产品风味维度时,必须用 flavorDimensions 声明维度名称和顺序:

groovy
android { flavorDimensions "version", "environment" productFlavors { free { dimension "version" applicationIdSuffix ".free" } paid { dimension "version" applicationIdSuffix ".paid" } dev { dimension "environment" buildConfigField "String", "API_URL", "\"https://dev.api.example.com\"" } staging { dimension "environment" buildConfigField "String", "API_URL", "\"https://staging.api.example.com\"" } prod { dimension "environment" buildConfigField "String", "API_URL", "\"https://api.example.com\"" } } }

维度顺序决定 sourceSet 合并优先级——排在前面的维度优先级更高。上面的配置中 "version" 优先于 "environment",意味着 src/free/ 中的资源会覆盖 src/dev/ 中的同名资源。

这个配置会生成 2 × 3 × 2 = 12 个变体:freeDevDebug、freeDevRelease、freeStagingDebug ... paidProdRelease。

sourceSet 合并优先级规则

Gradle 合并资源时按以下优先级从高到低查找:

  1. src/freeDevDebug/ — 构建变体源集(最具体,优先级最高)
  2. src/debug/ — 构建类型源集
  3. src/free/ — 产品风味源集(高优先级维度)
  4. src/dev/ — 产品风味源集(低优先级维度)
  5. src/main/ — 主源集(最低优先级)

优先级高的源集中的同名资源会覆盖优先级低的。需要注意:不同源集中不能存在同名的 Java/Kotlin 类,否则构建报错;但资源文件(strings、drawables)可以覆盖。

变体过滤:剔除无意义组合

多维度组合容易产生不需要的变体,用 variantFilter 过滤:

groovy
android { variantFilter { variant -> def names = variant.flavors*.name // 不需要 paid + dev 的组合 if (names.contains("paid") && names.contains("dev")) { variant.setIgnore(true) } } }

过滤后,paidDevDebug 和 paidDevRelease 不会出现在构建变体列表中,减少干扰。

多环境配置的常见写法

实际项目中,多环境通常这样组织:

groovy
android { buildTypes { debug { applicationIdSuffix ".debug" debuggable true minifyEnabled false } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } flavorDimensions "environment" productFlavors { dev { dimension "environment" applicationIdSuffix ".dev" resValue "string", "app_name", "MyApp-Dev" buildConfigField "String", "BASE_URL", "\"https://dev.example.com/api\"" manifestPlaceholders = [ENV_NAME: "dev"] } staging { dimension "environment" applicationIdSuffix ".staging" resValue "string", "app_name", "MyApp-Staging" buildConfigField "String", "BASE_URL", "\"https://staging.example.com/api\"" manifestPlaceholders = [ENV_NAME: "staging"] } prod { dimension "environment" resValue "string", "app_name", "MyApp" buildConfigField "String", "BASE_URL", "\"https://api.example.com\"" manifestPlaceholders = [ENV_NAME: "prod"] } } }

关键配置项说明:

  • applicationIdSuffix:让不同环境可共存在同一设备上
  • resValue:在构建时生成资源,代码中通过 R.string.app_name 引用
  • buildConfigField:在 BuildConfig 中生成常量,代码中通过 BuildConfig.BASE_URL 引用
  • manifestPlaceholders:向 AndroidManifest.xml 注入占位符变量,常用于配置不同环境的 meta-data

变体特定依赖

Gradle 支持按变体维度声明依赖:

groovy
dependencies { // 构建类型维度 debugImplementation 'com.facebook.stetho:stetho:1.6.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' // 产品风味维度 devImplementation 'com.example:dev-tools:1.0.0' prodImplementation 'com.example:prod-analytics:1.0.0' // 构建变体维度(最细粒度) freeDevImplementation 'com.example:free-dev-helper:1.0.0' }

依赖粒度从粗到细:implementation → buildTypeImplementation → flavorImplementation → variantImplementation。只在需要的变体中引入依赖,可以减小无用包体积。

常见踩坑

变体爆炸:3 个 flavorDimensions 各 3 个风味 + 2 个 buildTypes = 54 个变体。务必用 variantFilter 过滤无用组合,否则构建时间变长、AS 变体选择器卡顿。

源集类冲突src/free/src/paid/ 中不能有同名 Java 类。如果需要不同实现,要么放在各自的源集中,要么用接口 + 工厂模式在 main 中统一调度。

资源覆盖陷阱:高优先级源集中的 strings.xml 不是整个文件覆盖,实际上资源合并是条目级别的,同名 key 会被覆盖,不同 key 会合并。但 Java/Kotlin 类不行,同包同类名直接报错。

manifestPlaceholders 未生效:确保在 productFlavors 中赋值时用的是 = 而非闭包语法,且 AndroidManifest.xml 中用 ${ENV_NAME} 引用。

标签:Gradle