什么是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控制构建过程的行为(如是否启用混淆、签名、调试信息等),默认有debug和release两种常见类型。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)