news 2026/9/15 10:32:33

Android buildTypes和productFlavors实现差异化打包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android buildTypes和productFlavors实现差异化打包

什么是buildTypes和productFlavors

https://developer.android.com/build/build-variants?hl=zh-cn

1.buildTypes

buildTypes主要用于定义不同的构建变体,通常用于区分不同的构建模式,如开发版、发布版等。每个buildType都会有一组配置项,通常是与构建过程和构建输出相关的配置。常见的buildTypes包括:

  • debug:用于调试构建,通常包含日志和调试功能。
  • release:用于发布构建,通常会启用混淆、签名、优化等。

一共这些方法

https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/BuildType

buildTypes定义的是构建过程的行为,它影响的是最终的 APK 构建方式,例如签名方式、混淆规则等。

android {
buildTypes {
debug {
minifyEnabled false // 不启用混淆
debuggable true
}
release {
minifyEnabled true // 启用混淆
shrinkResources true // 去除未使用的资源
signingConfig signingConfigs.release
}
}
}

2.productFlavors

productFlavors用于定义不同的产品变体,通常是用来支持不同的产品版本或配置,比如免费版和付费版、不同地区的版本等。通过productFlavors,你可以为不同的版本提供不同的代码、资源或配置。

productFlavors允许你在同一个项目中创建多个产品变体,每个变体可以有不同的资源、应用 ID、版本号等。

android {
flavorDimensions "version"
productFlavors {
free {
applicationId "com.example.app.free"
versionName "1.0-free"
}
paid {
applicationId "com.example.app.paid"
versionName "1.0-paid"
}
}
}

区别总结:

  • buildTypes控制构建过程的行为(如是否启用混淆、签名、调试信息等),默认有debugrelease两种常见类型。
  • productFlavors控制应用的版本和特性(如免费版、付费版、不同地区版本等),让你可以为不同的目标配置不同的资源和代码。

在安卓开发过程中,难免会遇到像以下这样的一些需求:

1.需要打不同市场的包像opp vivo等,用于友盟统计各个市场的下载量

2.需要打测试包,生产包等,要求每个报名下的app名称,应用图标,appid ,url不同,甚至代码

像以上的一些需求我们称之为Android的差异化打包

现在我们一起来配置,下面的图是我配置好的

productFlavors{ //todo a ,b,c,d等是打包提供的一些标志名称,打包的时候可以选择 a{ applicationId "com.example.myapplication1" resValue "string", "app_name", "测试a" buildConfigField 'String', 'BASE_URL', '"我时a 的路由"' manifestPlaceholders=[ APPICON : "@mipmap/ic_launcher", ] } b{ applicationId "com.example.myapplication2" resValue "string", "app_name", "测试b" buildConfigField 'String', 'BASE_URL', '"我时b 的路由"' manifestPlaceholders=[ APPICON : "@mipmap/down", ] } c{ applicationId "com.example.myapplication3" resValue "string", "app_name", "测试c" buildConfigField 'String', 'BASE_URL', '"我时c 的路由"' manifestPlaceholders=[ APPICON : "@mipmap/up", ] } d{ applicationId "com.example.myapplication4" //修改application resValue "String", "app_name", "测试d" //todo 修改res中的资源 buildConfigField 'String', 'BASE_URL', '"我时d 的路由"' //todo 用来根据不同的包,更换不同的utl manifestPlaceholders=[ APPICON : "@mipmap/app_icon_mail", //todo 清单文件中映射的值 ] } }

然后运行的时候运行和打包的时候可以选择,这里以运行为列,点击如下按钮,可以选择当前运行包的环境

介绍完productFlavors 后,我们现在带着问题来解释下他的一些属性和用法

问题1:

manifestPlaceholders 占位符的使用,其实很好理解,你可以认为它可以在build.gradle文件中定义字符串并将值映射到AndroidManifest清单文件的指定位置.

如何使用呢?

首先在AndroidManifest中设置需要替代的字段

然后在build.gradle中设置清单文件中替代文字需要映射的值

d{ manifestPlaceholders=[ APPICON : "@mipmap/app_icon_mail", //todo 清单文件中映射的值 ] }

问题2:

resValue 这个指定后,会在build完成后,会在build文件里面生成值,例如
resValue "String", "app_name", "测试a" //他会在build文件夹生成如下
<string name="app_name" translatable="false">测试b</string>

如果你本里的values文件夹下存在了该

<string name="app_name">ExpandedRecycleViewDemo</string>

那么程序则会报错,因为build时会存在两个app_name的name

buildConfigField这个属性很好用,这个属性在build后会生成一个buildConfig.java的文件里面有许多可以使用的信息,因此我们可以把BASEURL设置在此,我的是如下配置
d{ applicationId "com.example.myapplication4" //修改application resValue "String", "app_name", "测试d" //todo 修改res中的资源 buildConfigField 'String', 'BASE_URL', '"我时d 的路由"' //todo 用来根据不同的包,更换不同的utl manifestPlaceholders=[ APPICON : "@mipmap/app_icon_mail", //todo 清单文件中映射的值 ] }

接下来看下buildConfig的文件

public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "com.example.myapplication2"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = "b"; public static final int VERSION_CODE = 1; public static final String VERSION_NAME = "1.0"; // Fields from product flavor: b public static final String BASE_URL = "我时b 的路由"; }

变生成了我设置的baseURL ,

以后就可以根据不同的打包环境生成不同的BASEURL

使用变体感知型依赖项管理机制 (这里指的是module ,不是线上的依赖库)

Android Gradle 插件 3.0.0 及更高版本包含一种新的依赖项机制,该机制可在使用库时自动匹配变体。这意味着,应用的debug变体会自动使用库的debug变体,依此类推。这种机制在使用变种时也同样适用:应用的freeDebug变体将使用库的freeDebug变体。

为了让插件准确匹配变体,您需要在无法进行直接匹配的情况下,按照以下部分中所述提供匹配回退机制。

例如,假设您的应用配置了一个名为“staging”的 build 类型,但该应用的一个库依赖项没有进行相应配置。当插件尝试构建“staging”版本的应用时,它不知道要使用哪个版本的库,因此您将看到一条与以下内容类似的错误消息

Error:Failed to resolve: Could not resolve project :mylibrary. Required by: project :app
解决与变体匹配相关的构建错误 :

您的应用包含库依赖项不包含的 build 类型

例如,您的应用包含“staging”build 类型,但依赖项仅包含“debug”和“release”build 类型。

请注意,如果库依赖项包含您的应用不包含的 build 类型,这不会引发问题。这是因为,插件在任何时候都不会从依赖项请求该 build 类型。

使用matchingFallbacks为给定的 build 类型指定替代匹配项,如下所示:

app模块

buildTypes { register("benchmark") { isMinifyEnabled = true signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) multiDexKeepProguard = file("multidex-rules.pro") matchingFallbacks += listOf("debug", "release") } release { isMinifyEnabled = true isShrinkResources = true // isDebuggable = true signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) multiDexKeepProguard = file("multidex-rules.pro") } debug { // buildConfigField("boolean","name","2") isMinifyEnabled = true applicationIdSuffix = ".debug" // 调试版应用 ID 后缀 signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) multiDexKeepProguard = file("multidex-rules.pro") } create("pre") { // buildConfigField("boolean","name","1") isDebuggable = true } }
module模块
buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } }
module模块的buildTyp比 app少了benchmark

所以选择benchmark编译的时候会报错,

这个时候在app下添加,就行

matchingFallbacks += listOf("debug", "release")。// 选择benchmark模块的时候,其他module没有benchmark,则从listOf("debug", "release")集合中取,优先debug

# Android Product Flavors 与 Source Sets 本文说明 Android Gradle 中 `productFlavors`、`sourceSets`、`main` 和 `buildTypes` 的关系,并结合 Majlis 项目的渠道结构给出示例。 ## 1. 核心概念 可以把它们简单理解为: ```text productFlavors:声明有哪些产品渠道 sourceSets:指定各个渠道的代码和资源存放位置 main:所有渠道共用的代码和资源 buildTypes:区分 debug、release 等构建类型 ``` `productFlavors` 不会根据已有目录自动生成。必须先在 Gradle 中声明 flavor,Android Gradle 插件才会为它创建同名 Source Set,并按照约定查找目录。 ## 2. 当前项目的 Flavor 结构 `libmajlis/build.gradle` 中定义了三个 flavor 维度: ```gradle flavorDimensions = ["log", "style", "buy"] productFlavors { localLog { dimension "log" } buglyLog { dimension "log" } yellow { dimension "style" buildConfigField "int", "SKIN_STYLE", "103" } blue { dimension "style" buildConfigField "int", "SKIN_STYLE", "104" } google { dimension "pay" } huawei { dimension "pay" } } ``` 每个完整构建变体都会从每个维度中选择一个 flavor,再选择一个 build type。 例如: ```text localLog + yellow + google + release ↓ localLogYellowGoogleRelease ``` ```text buglyLog + blue + huawei + debug ↓ buglyLogBlueHuaweiDebug ``` ## 3. Flavor 与 Source Set 的关系 声明一个 flavor: ```gradle productFlavors { google { dimension "pay" } } ``` Gradle 会自动创建逻辑上的 `sourceSets.google`,默认读取: ```text src/google/java/ src/google/kotlin/ src/google/res/ src/google/assets/ src/google/jniLibs/ src/google/AndroidManifest.xml ``` 使用标准目录时,不需要再显式配置: ```gradle sourceSets { google { ... } } ``` 如果需要使用自定义目录,才需要配置 `sourceSets`: ```gradle sourceSets { google { java.srcDirs = ["channel/google/java"] res.srcDirs = ["channel/google/res"] assets.srcDirs = ["channel/google/assets"] jniLibs.srcDirs = ["channel/google/jniLibs"] manifest.srcFile "channel/google/AndroidManifest.xml" } } ``` 因此,目录本身不会创建 flavor。例如新建: ```text src/AAA/BBB/ ``` `BBB` 不会自动成为 product flavor。如果希望它成为 flavor,必须先声明: ```gradle flavorDimensions = ["channel"] productFlavors { bbb { dimension "channel" } } ``` 推荐使用标准目录: ```text src/bbb/java/ src/bbb/res/ ``` 如果必须保留 `src/AAA/BBB`,可以手动映射: ```gradle sourceSets { bbb { java.srcDirs = ["src/AAA/BBB/java"] res.srcDirs = ["src/AAA/BBB/res"] } } ``` ## 4. main 是什么 `main` 不是 product flavor,而是 Android Gradle 插件自动创建的公共 Source Set。 每个 Android 模块都默认拥有: ```text src/main/java/ src/main/res/ src/main/assets/ src/main/jniLibs/ src/main/AndroidManifest.xml ``` `main` 会参与所有构建变体。 当前项目对 `main` 做了扩展: ```gradle sourceSets { main { java.srcDirs = [ "src/main/java", "src/main/java-config", "src/main/java-core", "src/main/java-db", "src/main/java-uikit", "src/main/java-utilkit", "src/main/java-effect", "src/main/java-webview", "src/main/java-selectpicture", "src/main/java-svga", "src/main/java-utils", "src/main/java-imagepreview", ] res.srcDirs = [ "src/main/res", "src/main/res-uikit", "src/main/res-effect", "src/main/res-webview", "src/main/res-selectpicture", "src/main/res-svga", "src/main/res-utils", "src/main/res-imagepreview", ] } } ``` 这些目录全部属于公共 `main`,因此会进入 Google、Huawei、Debug 和 Release 等所有变体。 可以类比为: ```text defaultConfig = 所有变体共用的构建配置 sourceSets.main = 所有变体共用的代码和资源 ``` ## 5. debug 和 release 也有 Source Set `debug`、`release` 属于 build type。Gradle 会自动创建: ```text src/debug/java/ src/debug/res/ src/debug/AndroidManifest.xml src/release/java/ src/release/res/ src/release/AndroidManifest.xml ``` 构建 `localLogOliveGoogleDebug` 时,主要合并: ```text src/main/ src/localLog/ src/olive/ src/google/ src/debug/ ``` 构建 `localLogOliveGoogleRelease` 时,主要合并: ```text src/main/ src/localLog/ src/olive/ src/google/ src/release/ ``` Debug 和 Release 可以分别提供相同类的不同实现: ```text src/debug/java/com/example/DebugTool.kt src/release/java/com/example/DebugTool.kt ``` 因为二者不会同时参与编译。这种结构适合让 Debug 使用真正的调试工具,而 Release 使用空实现。 不要同时在 `main` 和某个参与构建的 flavor/build type 中声明包名、类名完全相同的 Java/Kotlin 类,否则会出现重复类错误。 ## 6. Google 与 Huawei 代码如何隔离 项目分别提供: ```text src/google/java/... src/huawei/java/... ``` 构建 Google 变体时: ```text 公共代码 = src/main/ 等 main 目录 渠道代码 = src/google/ 排除代码 = src/huawei/ ``` 构建 Huawei 变体时: ```text 公共代码 = src/main/ 等 main 目录 渠道代码 = src/huawei/ 排除代码 = src/google/ ``` Google 和 Huawei 可以分别提供相同包名、相同类名的实现,例如: ```text src/google/java/com/common/support/buy/PayProcessor.kt src/huawei/java/com/common/support/buy/PayProcessor.kt ``` 因为两个 pay flavor 属于同一个维度,不会同时编译。公共代码可以调用统一的 `PayProcessor`,实际实现由构建渠道决定。 ## 7. 资源同名时的处理 ### 7.1 Flavor 资源与 main 资源同名 例如: ```text src/main/res/drawable/logo.png src/google/res/drawable/logo.png ``` Google 变体使用 `src/google` 中的资源;其他没有定义同名资源的渠道继续使用 `src/main` 中的资源。 字符串、布局、颜色等资源也遵循相同的覆盖规则。 资源合并优先级大致为: ```text 具体构建变体 > buildType(debug/release) > productFlavor > main > 第三方依赖 ``` 多个 flavor 维度之间的优先级由 `flavorDimensions` 的声明顺序决定。当前项目是: ```text log > style > pay ``` 不建议依赖跨维度同名资源覆盖,因为资源来源会变得难以判断。 ### 7.2 同一个 Source Set 的多个资源目录中出现同名资源 例如 `sourceSets.main.res.srcDirs` 同时包含: ```text src/main/res/drawable/logo.png src/main/res-uikit/drawable/logo.png ``` 两个目录都属于 `main`,优先级相同,一般会产生 `Duplicate resources` 编译错误。 ### 7.3 Java/Kotlin 类同名 Java/Kotlin 类不会像 Android 资源一样覆盖。 下面的结构在 Google 构建中会报重复类错误: ```text src/main/java/com/example/Pay.kt src/google/java/com/example/Pay.kt ``` 下面的结构是允许的: ```text src/google/java/com/example/Pay.kt src/huawei/java/com/example/Pay.kt ``` 因为 Google 和 Huawei 源集不会同时编译。 ### 7.4 Manifest `src/main/AndroidManifest.xml` 和渠道、build type 中的 Manifest 会进行合并,而不是整个文件替换。 高优先级 Manifest 可以覆盖低优先级属性。无法自动解决的冲突需要使用 `tools:replace`、`tools:remove` 等 Manifest Merger 标记处理。 ## 8. 渠道依赖 Source Set 决定编译哪些代码,依赖配置决定打包哪些第三方库,两者是独立的。 普通依赖会进入所有渠道: ```gradle implementation "group:name:version" ``` 仅 Google 渠道引入: ```gradle googleImplementation "com.android.billingclient:billing-ktx:8.3.0" ``` 仅 Huawei 渠道引入: ```gradle huaweiImplementation "com.huawei.hms:iap:6.13.0.300" huaweiImplementation "com.huawei.hms:hmscoreinstaller:6.11.0.302" ``` 仅 Debug 引入: ```gradle debugImplementation "io.github.didi.dokit:dokitx:3.7.11" ``` 仅 Release 引入: ```gradle releaseImplementation "group:name:version" ``` 当前项目的 Google/Huawei 支付代码已经通过 `src/google`、`src/huawei` 隔离,但支付依赖如果继续使用普通 `implementation`,两套 SDK 仍会进入所有渠道。因此支付依赖也应按照 flavor 分开配置。 ## 9. 常用目录与配置对应关系 | Gradle 配置 | 默认 Source Set/目录 | 用途 | |---|---|---| | Android 插件内置 | `src/main/` | 所有变体的公共代码和资源 | | `buildTypes.debug` | `src/debug/` | Debug 专属内容 | | `buildTypes.release` | `src/release/` | Release 专属内容 | | `productFlavors.google` | `src/google/` | Google 渠道内容 | | `productFlavors.huawei` | `src/huawei/` | Huawei 渠道内容 | | `sourceSets.google` | 自定义 | 修改 Google 源集的物理路径 | | `googleImplementation` | 无目录 | Google 渠道专属依赖 | | `debugImplementation` | 无目录 | Debug 专属依赖 | ## 10. 总结 ```text productFlavors ├── 声明渠道名称和所属维度 ├── 与 buildTypes 组合生成构建变体 ├── 自动创建同名 Source Set ├── 自动创建 googleImplementation 等依赖配置 └── 可以注入 BuildConfig、资源值、Manifest 参数等配置 sourceSets ├── main 是所有变体的公共源集 ├── flavor 和 build type 也有同名源集 └── 用于指定每个源集的实际代码、资源和 Manifest 路径 ``` 使用标准的 `src/<sourceSet名称>/` 目录时不需要额外配置 `sourceSets`;只有目录不符合标准约定时,才需要手动配置路径。 ## 参考资料 - [Android Developers:配置构建变体](https://developer.android.com/build/build-variants) - [Android Developers:添加构建依赖项](https://developer.android.com/build/dependencies)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:31:58

低功耗策略的收益与风险平衡:工程落地黄金点判定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 10:30:27

yq 如何在 Linux 上通过 wget 下载预编译二进制并安装到 PATH

yq 如何在 Linux 上通过 wget 下载预编译二进制并安装到 PATH 【免费下载链接】yq yq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor 项目地址: https://gitcode.com/GitHub_Trending/yq/yq yq 是一个用 Go 编写的命令行处理器…

作者头像 李华
网站建设 2026/9/15 10:29:30

Java类型转换实战:处理带单位字符串的工业级方案

1. 项目概述&#xff1a;当Integer遇上"12.5kg"的魔幻现实上周五深夜11点&#xff0c;我的企业微信突然被十几条报警信息轰炸。打开日志一看&#xff0c;某个核心业务模块的失败率飙升到47%。追查下去&#xff0c;发现是第三方物流系统返回的"12.5kg"字符串…

作者头像 李华
网站建设 2026/9/15 10:26:29

基于Java的民宿管理系统:从订单防超卖到并发控制的实战解析

简介&#xff1a;这份基于Java语言的民宿管理系统设计源码&#xff0c;面向有一定Java基础的开发者、毕业设计学生或中小型民宿业务管理者&#xff0c;用于理解民宿预订、房间管理、用户权限等核心模块的开发思路。压缩包共225个文件&#xff0c;大小33.57MB&#xff0c;其中包…

作者头像 李华
网站建设 2026/9/15 10:26:23

PLL相位噪声Matlab仿真与工程实践指南

1. 锁相环相位噪声仿真概述在射频电路和通信系统设计中&#xff0c;锁相环(PLL)的相位噪声性能直接影响整个系统的信号质量。作为频率合成器的核心部件&#xff0c;PLL的相位噪声会通过混频、倍频等操作传递到系统输出端。Matlab仿真成为工程师在物理实现前评估PLL性能的重要工…

作者头像 李华