构建变体:构建类型、产品风味与变体感知依赖
说实话,构建变体这个概念,我第一次接触时也觉得有点绕。但用熟了之后,你会发现它简直是多环境、多渠道发布的利器。今天我们就把它彻底讲透。
构建类型:Debug 与 Release 的底层逻辑
构建类型,说白了就是同一份代码在不同场景下的「打包配置」。最常见的两个就是 Debug 和 Release。
我个人习惯在 build.gradle 里这样配置:
android {
buildTypes {
debug {
// 可调试,不混淆
debuggable true
minifyEnabled false
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
release {
// 生产环境,混淆+压缩
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
这里有个细节:applicationIdSuffix 和 versionNameSuffix 是 Debug 构建类型的标配。为什么?因为你可以同时安装 Debug 和 Release 版本到手机上,不会冲突。我在项目中遇到过好几次,测试人员反馈「我装了新版怎么还是老样子」,结果发现装的是 Release 包——嗯,从那以后我强制所有 Debug 包都带后缀。
crashlytics 的调试模式,但 Release 不会。如果你在 Debug 下看到崩溃日志特别详细,别慌,这是正常的。
产品风味:Flavor 的实战用法
产品风味解决的是「同一套代码,不同渠道/不同定制」的问题。比如你的 App 要上架华为、小米、Google Play,每个渠道的包名、图标、API 地址都不一样。用 Flavor 就对了。
看一个我实际用过的配置:
android {
flavorDimensions "market", "apiLevel"
productFlavors {
huawei {
dimension "market"
applicationId "com.example.app.huawei"
manifestPlaceholders = [app_name: "我的应用-华为版"]
}
xiaomi {
dimension "market"
applicationId "com.example.app.xiaomi"
manifestPlaceholders = [app_name: "我的应用-小米版"]
}
google {
dimension "market"
applicationId "com.example.app.google"
manifestPlaceholders = [app_name: "My App"]
}
minApi21 {
dimension "apiLevel"
minSdkVersion 21
}
minApi26 {
dimension "apiLevel"
minSdkVersion 26
}
}
}
你想想看,这样组合出来有多少种变体?3 个市场 × 2 个 API 等级 = 6 种。Gradle 会自动生成对应的任务,比如 assembleHuaweiMinApi21Debug。
变体过滤:只构建你需要的
项目大了之后,全量构建所有变体会非常慢。我记得有一次,项目有 4 个维度、每个维度 3 个选项,总共 81 种变体。每次 assemble 都要跑十几分钟。后来我用了变体过滤,只保留需要的几种。
过滤方式有两种:
- 在
build.gradle中配置过滤规则 - 在命令行中指定变体名称
第一种方式:
android {
variantFilter { variant ->
def names = variant.flavors*.name
// 如果市场是 google 且 API 等级是 minApi21,就跳过
if (names.contains("google") && names.contains("minApi21")) {
setIgnore(true)
}
// 只保留 Debug 和 Release,跳过其他构建类型
if (variant.buildType.name != "debug" && variant.buildType.name != "release") {
setIgnore(true)
}
}
}
第二种方式更直接:
./gradlew assembleHuaweiMinApi26Release
为什么推荐第二种?因为 CI/CD 环境里,你只需要构建一个特定的变体,没必要在 Gradle 文件里写死过滤逻辑。我曾经踩过一个坑:在 variantFilter 里过滤掉了某些变体,结果 QA 说「为什么这个渠道的包打不出来了?」——嗯,从那以后我尽量不在代码里写死过滤,而是交给 CI 脚本控制。
变体感知依赖:Gradle 的智能匹配
这是 Gradle 最强大的特性之一。说白了,就是 Gradle 会根据你当前构建的变体,自动选择对应的依赖版本。比如 Debug 下用测试库,Release 下用生产库。
配置方式很简单:
dependencies {
// 所有变体都依赖
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
// 仅 Debug 变体依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
// 仅 Release 变体依赖
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.7'
// 特定 Flavor 变体依赖
huaweiImplementation 'com.huawei.hms:push:5.3.0.301'
xiaomiImplementation 'com.xiaomi.mipush:sdk:4.2.0'
}
你想想看,这有多方便?Debug 下自动引入 LeakCanary 检测内存泄漏,Release 下自动换成空实现,完全不用手动切换。我在项目中遇到过最典型的场景是:Debug 下用 stetho 调试数据库,Release 下去掉——一行配置搞定。
变体感知依赖的匹配规则是这样的:
| 依赖声明 | 匹配的变体 |
|---|---|
implementation |
所有变体 |
debugImplementation |
所有 Debug 构建类型 |
releaseImplementation |
所有 Release 构建类型 |
huaweiImplementation |
所有包含 huawei Flavor 的变体 |
huaweiDebugImplementation |
仅 huawei + Debug 组合 |
这里有个容易踩的坑:如果你同时声明了 huaweiImplementation 和 debugImplementation,Gradle 会优先匹配更具体的那个。比如 huaweiDebugImplementation 的优先级最高。我曾经因为没搞清楚这个优先级,导致 Debug 下用了 Release 的依赖,排查了半天才发现是声明顺序的问题。
<flavorName><buildType>Implementation。注意大小写:Flavor 首字母大写,BuildType 首字母小写。比如 huaweiDebugImplementation 是正确的,HuaweiDebugImplementation 会报错。
知识体系总览
下面这张图是我自己总结的构建变体核心逻辑,你可以对照着理解:
从这张图你可以看到,构建类型和产品风味是「输入」,变体过滤是「控制开关」,变体感知依赖是「输出结果」。整个流程是串起来的,缺一不可。
最后说一句:构建变体这个功能,用好了能极大提升开发效率。但别一开始就搞太复杂,从 2-3 个变体开始,慢慢扩展。我见过有人一上来就搞 20 个变体,结果 CI 跑一次要 40 分钟——嗯,那就不叫效率了,叫灾难。
公众号:蓝海资料掘金营,微信deep3321