Product Flavors 配置
Product Flavors(产品变种)用于创建应用的不同版本,每个版本可以有不同的功能、资源配置和依赖项。通过产品变种,您可以从同一代码库构建多个版本的应用,例如免费版和付费版、不同品牌的定制版本等。
核心概念
产品变种是什么
产品变种代表了应用程序的不同版本,这些版本可以在设备上共存、独立分发到 Google Play,或针对不同市场发布。每个变种可以指定:
- 不同的 Application ID
- 不同的功能集
- 不同的资源(图标、字符串、布局等)
- 不同的依赖项
- 不同的最低 SDK 版本
产品变种 vs Build Types
| 维度 | Product Flavors | Build Types | |------|----------------|-------------| | 用途 | 创建应用的不同版本(免费/付费、品牌定制) | 开发生命周期阶段(调试/发布) | | Application ID | 通常不同 | 通常相同(可添加后缀) | | 资源差异 | 功能性差异 | 优化程度差异 | | 典型数量 | 2-5 个 | 2-3 个 | | 示例 | free, paid, demo, full | debug, release, staging |
Build Variants 生成规则
Build Variant = Product Flavor × Build Type
// 假设有:
// Product Flavors: demo, full
// Build Types: debug, release
// 将自动生成 4 个 Build Variants:
// 1. demoDebug
// 2. demoRelease
// 3. fullDebug
// 4. fullRelease配置 Product Flavors
单一维度变种 AGP 3.0+
从 AGP 3.0 开始,所有产品变种必须属于一个命名的变种维度。对于简单场景,只需一个维度即可。
android {
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
// 声明变种维度(必需)
flavorDimensions += "version"
productFlavors {
create("demo") {
// 分配给 "version" 维度
dimension = "version"
// 添加 Application ID 后缀
applicationIdSuffix = ".demo"
versionNameSuffix = "-demo"
// 可以覆盖 defaultConfig 中的任何属性
minSdk = 21
}
create("full") {
dimension = "version"
applicationIdSuffix = ".full"
versionNameSuffix = "-full"
}
}
}android {
defaultConfig {
applicationId 'com.example.myapp'
minSdk 24
targetSdk 36
versionCode 1
versionName '1.0'
}
flavorDimensions 'version'
productFlavors {
demo {
dimension 'version'
applicationIdSuffix '.demo'
versionNameSuffix '-demo'
minSdk 21
}
full {
dimension 'version'
applicationIdSuffix '.full'
versionNameSuffix '-full'
}
}
}生成的 Build Variants:
demoDebug→com.example.myapp.demo(debug)demoRelease→com.example.myapp.demo(release)fullDebug→com.example.myapp.full(debug)fullRelease→com.example.myapp.full(release)
多维度变种
当需要组合多个产品变种的配置时,可以使用多个变种维度。Gradle 会为每个维度的变种和构建类型的所有组合创建构建变体。
android {
defaultConfig {
applicationId = "com.example.myapp"
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("debug") { }
getByName("release") { }
}
// 定义两个变种维度,顺序决定优先级
// 第一个维度优先级最高
flavorDimensions += listOf("api", "mode")
productFlavors {
// "mode" 维度:应用版本类型
create("demo") {
dimension = "mode"
applicationIdSuffix = ".demo"
}
create("full") {
dimension = "mode"
applicationIdSuffix = ".full"
}
// "api" 维度:支持的 API 级别
// "api" 维度的配置会覆盖 "mode" 维度的配置
create("minApi24") {
dimension = "api"
minSdk = 24
versionCode = 30000 + (android.defaultConfig.versionCode ?: 0)
versionNameSuffix = "-minApi24"
}
create("minApi23") {
dimension = "api"
minSdk = 23
versionCode = 20000 + (android.defaultConfig.versionCode ?: 0)
versionNameSuffix = "-minApi23"
}
create("minApi21") {
dimension = "api"
minSdk = 21
versionCode = 10000 + (android.defaultConfig.versionCode ?: 0)
versionNameSuffix = "-minApi21"
}
}
}android {
defaultConfig {
applicationId 'com.example.myapp'
versionCode 1
versionName '1.0'
}
buildTypes {
debug { }
release { }
}
flavorDimensions 'api', 'mode'
productFlavors {
demo {
dimension 'mode'
applicationIdSuffix '.demo'
}
full {
dimension 'mode'
applicationIdSuffix '.full'
}
minApi24 {
dimension 'api'
minSdk 24
versionCode 30000 + android.defaultConfig.versionCode
versionNameSuffix '-minApi24'
}
minApi23 {
dimension 'api'
minSdk 23
versionCode 20000 + android.defaultConfig.versionCode
versionNameSuffix '-minApi23'
}
minApi21 {
dimension 'api'
minSdk 21
versionCode 10000 + android.defaultConfig.versionCode
versionNameSuffix '-minApi21'
}
}
}生成的 Build Variants(共 12 个):
命名格式:<api-flavor><mode-flavor><BuildType>
minApi24DemoDebug、minApi24DemoReleaseminApi24FullDebug、minApi24FullReleaseminApi23DemoDebug、minApi23DemoReleaseminApi23FullDebug、minApi23FullReleaseminApi21DemoDebug、minApi21DemoReleaseminApi21FullDebug、minApi21FullRelease
维度优先级:
flavorDimensions列表中的排列顺序决定优先级- 优先级高的维度:
- 名称排在前面
- 配置可覆盖优先级低的维度
- 源代码集合并时具有更高权重
Product Flavor 常用属性
Application ID 相关
productFlavors {
create("free") {
// 设置完整的 Application ID
applicationId = "com.example.myapp.free"
// 或添加后缀(更常用)
applicationIdSuffix = ".free"
}
}版本相关
productFlavors {
create("demo") {
// 覆盖版本号
versionCode = 1000
versionName = "1.0-demo"
// 或添加后缀
versionNameSuffix = "-demo"
}
}SDK 版本
productFlavors {
create("legacy") {
// 支持更低的 API 级别
minSdk = 21
targetSdk = 33
}
create("modern") {
minSdk = 24
targetSdk = 36
}
}BuildConfig 字段
android {
buildFeatures {
buildConfig = true
}
}
productFlavors {
create("free") {
buildConfigField("String", "API_KEY", "\"free-api-key-123\"")
buildConfigField("boolean", "ENABLE_ADS", "true")
buildConfigField("int", "MAX_USERS", "1")
}
create("paid") {
buildConfigField("String", "API_KEY", "\"paid-api-key-456\"")
buildConfigField("boolean", "ENABLE_ADS", "false")
buildConfigField("int", "MAX_USERS", "999")
}
}在代码中使用:
val apiKey = BuildConfig.API_KEY
if (BuildConfig.ENABLE_ADS) {
showAd()
}资源值
productFlavors {
create("free") {
resValue("string", "app_name", "\"MyApp Free\"")
resValue("string", "api_url", "\"https://free-api.example.com\"")
resValue("color", "brand_color", "\"#FF0000\"")
}
create("paid") {
resValue("string", "app_name", "\"MyApp Pro\"")
resValue("string", "api_url", "\"https://api.example.com\"")
resValue("color", "brand_color", "\"#0000FF\"")
}
}在 XML 中使用:
<TextView
android:text="@string/app_name"
android:textColor="@color/brand_color" />清单占位符
productFlavors {
create("dev") {
manifestPlaceholders["hostName"] = "dev.example.com"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher_dev"
}
create("prod") {
manifestPlaceholders["hostName"] = "example.com"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher"
}
}在 AndroidManifest.xml 中使用:
<application
android:icon="${appIcon}">
<meta-data
android:name="hostname"
android:value="${hostName}" />
</application>签名配置
productFlavors {
create("demo") {
signingConfig = signingConfigs.getByName("debug")
}
create("full") {
signingConfig = signingConfigs.getByName("release")
}
}详见 应用签名配置。
测试配置
productFlavors {
create("mock") {
testInstrumentationRunner = "com.example.MockTestRunner"
testApplicationId = "com.example.myapp.mock.test"
}
}多 APK 支持 不推荐,建议使用 AAB
productFlavors {
create("x86") {
ndk {
abiFilters += "x86"
}
versionCode = 1
}
create("arm") {
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
versionCode = 2
}
}过滤构建变体 AGP 4.0+
某些产品变种和构建类型的组合可能没有意义,可以使用 androidComponents 块过滤掉不需要的变体。
androidComponents {
beforeVariants { variantBuilder ->
// 过滤掉 demo 版本的 minApi21 变体
if (variantBuilder.productFlavors.containsAll(
listOf("api" to "minApi21", "mode" to "demo")
)) {
variantBuilder.enable = false
}
// 或者根据 Build Type 过滤
if (variantBuilder.buildType == "debug" &&
variantBuilder.productFlavors.contains("mode" to "paid")) {
variantBuilder.enable = false
}
}
}androidComponents {
beforeVariants { variantBuilder ->
if (variantBuilder.productFlavors.containsAll([api: 'minApi21', mode: 'demo'])) {
variantBuilder.enabled = false
}
}
}实践案例
案例一:免费版和付费版
android {
flavorDimensions += "tier"
productFlavors {
create("free") {
dimension = "tier"
applicationIdSuffix = ".free"
versionNameSuffix = "-free"
buildConfigField("boolean", "ENABLE_ADS", "true")
buildConfigField("boolean", "ENABLE_PREMIUM_FEATURES", "false")
buildConfigField("int", "MAX_PROJECTS", "3")
resValue("string", "app_name", "\"MyApp Free\"")
}
create("paid") {
dimension = "tier"
applicationIdSuffix = ".pro"
versionNameSuffix = "-pro"
buildConfigField("boolean", "ENABLE_ADS", "false")
buildConfigField("boolean", "ENABLE_PREMIUM_FEATURES", "true")
buildConfigField("int", "MAX_PROJECTS", "999")
resValue("string", "app_name", "\"MyApp Pro\"")
}
}
}
dependencies {
// 仅免费版包含广告 SDK
"freeImplementation"("com.google.android.gms:play-services-ads:22.6.0")
// 仅付费版包含高级功能库
"paidImplementation"(project(":premium-features"))
}案例二:多环境配置
android {
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
buildConfigField("boolean", "ENABLE_LOGGING", "true")
buildConfigField("boolean", "ENABLE_CRASH_REPORTING", "false")
manifestPlaceholders["appName"] = "MyApp DEV"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher_dev"
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")
buildConfigField("boolean", "ENABLE_LOGGING", "true"
)
buildConfigField("boolean", "ENABLE_CRASH_REPORTING", "true")
manifestPlaceholders["appName"] = "MyApp STAGING"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher_staging"
}
create("production") {
dimension = "environment"
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
buildConfigField("boolean", "ENABLE_LOGGING", "false")
buildConfigField("boolean", "ENABLE_CRASH_REPORTING", "true")
manifestPlaceholders["appName"] = "MyApp"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher"
}
}
}案例三:白标应用
android {
flavorDimensions += "brand"
productFlavors {
create("brandA") {
dimension = "brand"
applicationId = "com.branda.app"
resValue("string", "app_name", "\"Brand A App\"")
resValue("color", "brand_primary", "\"#FF6200EE\"")
resValue("color", "brand_secondary", "\"#FF03DAC5\"")
buildConfigField("String", "BRAND_NAME", "\"Brand A\"")
}
create("brandB") {
dimension = "brand"
applicationId = "com.brandb.app"
resValue("string", "app_name", "\"Brand B App\"")
resValue("color", "brand_primary", "\"#FFF44336\"")
resValue("color", "brand_secondary", "\"#FF009688\"")
buildConfigField("String", "BRAND_NAME", "\"Brand B\"")
}
}
}每个品牌使用不同的资源目录:
src/
├── main/ # 共享代码和资源
├── brandA/ # Brand A 特有资源
│ ├── res/
│ │ ├── drawable/
│ │ │ └── logo.png
│ │ └── values/
│ │ └── strings.xml
│ └── java/
└── brandB/ # Brand B 特有资源
├── res/
│ ├── drawable/
│ │ └── logo.png
│ └── values/
│ └── strings.xml
└── java/选择和构建变体
在 Android Studio 中选择
- 打开 Build > Select Build Variant
- 在 Build Variants 面板中选择目标变体
- 点击 Run 或 Debug 按钮
命令行构建
# 构建特定变体
./gradlew assembleDemoDebug
./gradlew assembleFullRelease
# 构建所有变体
./gradlew assembleDebug
./gradlew assembleRelease
./gradlew assemble
# 安装特定变体
./gradlew installDemoDebug常见问题
错误:All flavors must belong to a named flavor dimension AGP 3.0+
原因:从 AGP 3.0 开始,所有产品变种必须分配给一个变种维度。
解决方法:
android {
// 添加这行
flavorDimensions +="version"
productFlavors {
create("demo") {
// 分配维度
dimension = "version"
}
}
}如何为特定变种配置依赖
dependencies {
// 仅用于 demo 变种
"demoImplementation"("com.example:demo-lib:1.0")
// demo 变种的 debug 构建
"demoDebugImplementation"("com.example:debug-tools:1.0")
// 仅用于 full 变种的 release 构建
"fullReleaseImplementation"("com.example:analytics:1.0")
}详见 依赖管理 - 变体特定依赖。
如何在代码中判断当前变种
// 使用 BuildConfig
val flavor = BuildConfig.FLAVOR
when (flavor) {
"demo" -> {
// Demo 逻辑
}
"full" -> {
// Full 逻辑
}
}
// 更优雅的方式:使用 BuildConfig 字段
if (BuildConfig.ENABLE_PREMIUM_FEATURES) {
// 高级功能
}变种命名最佳实践
- 使用小写字母:
demo、full、free(不是Demo、FULL) 2 避免使用保留字:不要使用test、androidTest等 - 简短明确:
free比freeVersion更好 - 一致的命名规则:同一维度的变种使用相似模式
最佳实践
- 合理使用维度:大多数应用只需要 1-2 个变种维度
- Application ID 管理:为不同变种使用不同的 Application ID,允许多版本共存
- 共享代码最大化:将共享代码放在
main/源代码集,仅在必要时创建变种特定代码 - 版本号策略:为不同变种使用不同的版本号前缀(如 10000、20000)
- 过滤无用变体:使用
androidComponents过滤无意义的组合 - 依赖隔离:使用变种特定依赖避免无用库打包
- BuildConfig 优于硬编码:使用
buildConfigField管理环境配置
API 参考
- ProductFlavor - Product Flavor DSL 参考
- VariantFilter - 变体过滤 API