Android 开发中的 Product Flavor 是 Gradle 的一种机制,允许从同一代码库创建单个应用的多个变体。每个 flavor 可以拥有自己的 applicationId、资源、依赖项和功能 — 例如免费版和付费版。根据 Google Android Developers, 2025,Product Flavors 属于 Build Variants 系统,通过 flavorDimensions 与 Build Types 组合。这是在 Google Play 发布应用多个版本的标准方法。
要点
Product Flavor 是 android.productFlavors 块中的 Gradle 配置,用于描述产品变体。每个 flavor 可以覆盖 defaultConfig 的 applicationId、versionName、versionCode、minSdkVersion、targetSdkVersion、signingConfig 和其他参数。Product Flavors 的数量没有限制:一个项目可以包含 2、5、10 个 flavor — Gradle 会处理所有组合。
Product Flavor 解决了 codebase reuse(代码库复用)的问题 — 即从一个仓库构建多个不同的应用。典型场景:含广告的免费版和不含广告的付费版、功能受限的演示版、企业版和消费版、不同客户的白标应用。没有 Product Flavors,每个版本都需要在单独的项目中维护,导致 60-70% 的代码重复。
历史上,Product Flavors 出现在 Android Gradle Plugin 0.9(2013 年)中,作为 ant 配置的替代。在此之前,开发人员为不同版本使用独立项目或手动替换构建前的资源。在 AGP 中引入 flavor 统一了这种方法并使其成为标准。根据 JetBrains, 2024 的调查,78% 具有多个版本的 Android 项目使用 Product Flavors,其余使用通过 BuildConfig 或 reflection 手动切换。
Build Type 管理构建过程(debug 调试、release 优化)。Product Flavor 管理构建内容(free 无付费功能、paid 有付费功能)。Build Type 是基础设施设置,Product Flavor 是产品设置。两个概念是正交的:free-flavor 的 debug 构建与 release 构建仅在编译参数上不同,而非功能。Product Flavor 不能用于禁用调试器 — 这是 Build Type 的任务。
Flavor Dimensions 是将 Product Flavors 分组到独立类别中的机制。如果应用有免费/付费版本和独立的美洲/欧洲区域,则 flavor 分组为两个维度:“tier”(free、paid)和 “region”(us、eu)。Gradle 创建维度的笛卡尔积:freeUs、freeEu、paidUs、paidEu — 4 个变体。没有维度,Gradle 会将所有四个 flavor 视为一个平面,只能选择一个。
维度在 flavorDimensions 块中声明为字符串或字符串列表。维度的顺序影响 source sets 的优先级:第一个维度具有最高优先级。如果维度 A (tier) 列在前面,则在资源冲突的情况下 src/free/ 将覆盖 src/us/。此外,顺序影响 Variant 名称的形成方式:首先是第一个维度的 flavor,然后是第二个维度,然后是 Build Type:freeUsDebug。
维度的数量没有限制,但每个新维度都会增加 Build Variants 的数量。对于一个具有 4 个维度(各 2 个 flavor)和 2 个 build types 的项目,结果是 2 × 2 × 2 × 2 × 2 = 32 个变体。实际限制是 3 个维度(最多 8-12 个变体)。更多维度会使 Gradle 配置变慢,并且 Android Studio 中的 Build Variants 面板会变得难以阅读。
android {
flavorDimensions "tier", "api"
productFlavors {
free {
dimension "tier"
applicationId "com.example.app.free"
versionNameSuffix "-free"
}
paid {
dimension "tier"
applicationId "com.example.app.paid"
}
minApi21 {
dimension "api"
minSdk 21
}
minApi26 {
dimension "api"
minSdk 26
}
}
}
// 结果:freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// 每个 × debug/release = 8 个 Build Variants
要创建 Product Flavor,需要在 android 内部添加 productFlavors 块,指定 flavor 名称及其参数。最简 flavor 声明是名称和维度。所有其他参数从 defaultConfig 继承并可被覆盖。Flavor 完全继承 defaultConfig,包括 applicationId、versionCode、testInstrumentationRunner。
每个 flavor 可以覆盖 applicationId — 这允许将同一应用的多个版本同时安装到一台设备上。例如,free-版本为 com.example.app.free,paid 为 com.example.app.paid。如果未覆盖 applicationId,所有 flavor 将具有相同的标识符,无法并行安装。applicationId 必须与清单中的 package 匹配(如果未使用 applicationIdSuffix)。
AGP 8+ 建议使用 Kotlin DSL 而非 Groovy 编写 build.gradle。Kotlin DSL 提供类型安全的配置访问:IDE 提示参数名称、在编译时检查类型并高亮错误。从 Groovy 迁移到 Kotlin DSL 用于 Product Flavors 通常包括将引号替换为括号并添加类型。AGP 向后兼容 — 两种语法可在同一项目中并行使用。
// build.gradle.kts — Kotlin DSL
android {
flavorDimensions += "tier"
productFlavors {
register("free") {
dimension = "tier"
applicationId = "com.example.app.free"
versionNameSuffix = "-free"
buildConfigField("boolean", "IS_PREMIUM", "false")
}
register("paid") {
dimension = "tier"
applicationId = "com.example.app.paid"
versionNameSuffix = "-paid"
buildConfigField("boolean", "IS_PREMIUM", "true")
}
}
}
每个 Product Flavor 创建自己的 source set — src/<flavorName>/ 目录。此目录中可以放置覆盖的资源、源代码和清单。Flavor 的 source set 作为 main 之上的覆盖层:src/free/res/ 中的文件覆盖 src/main/res/ 中同名的文件。这允许每个 flavor 具有不同的字符串、图标、颜色和布局,而无需修改主代码。
覆盖 Java/Kotlin 类有两种方法:flavor-specific implementation(在每个 flavor 中实现抽象类)和 BuildConfig field(代码中分支判断)。第一种方法更清晰:在 main 中定义接口或抽象类,在 src/free/ 和 src/paid/ 中定义具体实现。构建时只编译当前 flavor 的实现。这带来双重优势:更小的 APK 大小(付费代码不会进入 free-版本)和安全性(无法意外调用付费功能)。
source set flavor 中的 AndroidManifest.xml 不会替换而是与主清单合并。合并遵循 Android 规则:同一元素中的相同属性被覆盖,唯一属性被添加。例如,如果主清单中声明了 INTERNET 权限而 free 中没有,互联网权限仍然保留。但 tools:node="replace" 允许替换特定 flavor 的整个清单块。当不同 flavor 需要不同权限时(paid 需要 SD 卡写入,free 需要相机),这非常有用。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="Free App"
tools:replace="android:label">
</application>
</manifest>
考虑一个典型场景:free — 含广告和基本功能的版本,paid — 无广告、功能更丰富的版本。free-版本设置 applicationId 为 "com.example.app.free",paid 为 "com.example.app.paid"。两个版本可以同时安装在同一设备上,因为 applicationId 是 Android 系统中应用的唯一标识符。
架构上,分离通过 interface + flavor implementation 实现。在 main source set 中声明接口 PaymentService。在 src/free/ 中是通过 AdMob 在支付前展示广告的实现。在 src/paid/ 中是直接进入支付网关的实现。使用 PaymentService 的代码不知道加载了哪个实现 — 这在编译时决定。这种方法保证了即使开发者意外调用,free-版本也不会包含订阅管理代码。
不同 flavor 的 APK 大小可能相差 5-15 MB,因为包含/排除了依赖项。要从特定 flavor 中排除库,在 build.gradle 中使用 flavor-specific dependencies:freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'。此依赖项仅添加到 free-变体,不会增加 paid-版本的大小。公共依赖项使用 implementation — 所有 flavor 都包含它们。
// src/main/kotlin/com/example/payment/PaymentService.kt
interface PaymentService {
fun processOrder(amount: Double, callback: (PaymentResult) -> Unit)
}
// src/free/kotlin/.../FreePaymentService.kt
class FreePaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
AdManager.showInterstitial {
PaymentGateway.charge(amount, callback)
}
}
}
// src/paid/kotlin/.../PaidPaymentService.kt
class PaidPaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
PaymentGateway.charge(amount, callback)
}
}
在多模块项目中,库模块可能没有自己的 Product Flavors,这会产生一个问题:库只编译一次(作为 release),而带有 flavor 的 app 模块期望库有相应的变体。从 AGP 8.1 开始,库可以通过 publishing.multipleVariants 块发布 multiple variants — 这允许将库的所有 flavor 变体发布到单个 maven 仓库,app 模块会自动选择所需的变体。
另一种方法是在库中声明与 app 模块相同的 flavorDimensions 和 productFlavors。AGP 根据来自一个维度的名称完全匹配自动映射 flavor。如果库中的 flavor 名称与 app 中的匹配,AGP 将创建一致的变体。为便于维护,建议将公共 flavor 定义提取到 Convention Plugin — 应用于项目所有模块的 Gradle 插件。
对于不用于发布(内部模块)的库,通过根项目的 build.gradle 同步 flavor 就足够了。Gradle 提供 subprojects 方法,允许将配置应用于所有子项目。但需要注意的是,subprojects 中过多的配置会减慢 configuration phase。建议使用 Convention Plugins — 它们编译一次并重复使用,将配置时间减少 15-30%。
常见问题
数量没有限制,但每个维度都会增加 Build Variants 的数量。一个维度中的 4 个 flavor + 2 个 build types = 8 个变体。两个维度中 4 + 4 = 16 个变体。建议不超过 3 个维度和 10-12 个总变体。
可以,通过 source set src/<flavor>/AndroidManifest.xml。清单与主清单合并。要替换整个块,使用 tools:node="replace"。例如,替换应用标签或特定 flavor 的权限。
使用 <flavorName>Implementation 配置。示例:freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'。此依赖项仅在构建 free-变体时包含。对于 paid:paidImplementation。公共依赖项通过 implementation 指定。
Product Flavor 定义 产品版本(free、paid、demo),Build Type 定义构建方式(debug、release)。Flavor 可以覆盖 applicationId、versionName、资源。Build Type 管理 debuggable、minification、signing。两者正交,组合成 Build Variant。
可以,Product Flavors 与 Compose 无限制地工作。不同 flavor 可以通过 source sets 或抽象类实现拥有不同的 Compose 界面。还可以添加特定 flavor 的 Compose 依赖项:freeImplementation 'androidx.compose.ui:ui-tooling'。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。