1. 什么是buildConfig
🔗
最近在学习Android开发,写小项目的时候想到了能不能实现APK的条件编译。条件编译在开发中是一种很寻常的一种需求,例如各大APP的开发商可能会按照分发渠道的不同打包含有不同部分特性和UI安装包,或者为测试安装包插入一些便于调试的测试代码。条件编译可以在构建期按照不同构建配置的信息构建含有不同代码逻辑的安装包,而不是让开发者去为多种构建目标分别维护一套代码。
在Android开发时我们通常使用Gradle完成项目的构建,严格来说Gradle本身并不支持条件编译,但是其通过buildConfig
特性实现了类似于条件编译的效果。在开启了Gradle的buildConfig
特性后,Gradle会为用户提供一个自动生成的类BuildConfig
,类似于R
这个类。BuildConfig
类中包含用户为不同环境配置的KEY-VALUE,为用户提供了一种通过对象访问构建环境信息的渠道。
2. 如何使用buildConfig
🔗
Gradle的buildConfig
构建特性可以用于在打包测试包时生成调试代码,以及动态生成构建版本号等。本文基于仅在测试包中生成测试用的点击事件的需求,介绍一下怎么在开发中使用buildConfig
构建特性。
2.1 启用buildConfig
构建特性 🔗
首先我们要在项目中启用buildConfig
这个构建特性,在Gradle配置文件(如build.gradle.kts
)中配置android.buildFeatures.buildConfig = true
。
android {
buildFeatures {
// 配置开启buildConfig构建特性
buildConfig = true
compose = true
}
}
2.2 添加buildConfigField
🔗
在开启buildConfig
构建特性后,我们可以为不同环境的构建创建不同的buildConfigField
,用于在代码中获知当前构建的构建信息。由于我们的需求是仅仅在测试环境实现点击事件,因此我们可以用一个field标识当前环境是否是测试环境。这个字段的配置是取决于开发者自己的,比如我们可以用DEBUG
这个field判断当前的测试环境,所以我们可以在release构建中将DEBUG
这个field设置为false
,而在release构建中将DEBUG
这个field设置为true。
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// 用于标识release环境条件编译
buildConfigField("Boolean", "DEBUG", "false")
}
debug {
// 用于标识debug环境条件编译
buildConfigField("Boolean", "DEBUG", "true")
}
}
}
2.3 业务代码条件判断 🔗
开启buildConfig
特性以及设置不同环境下的buildConfigField
的值之后,Gradle就为我们自动生成了一个名为BuildConfig
的类,如下图所示。从图中可以知道,其实生成的BuildConfig
还有几个默认的属性,包括构建类型、版本代码等,我们也可以直接比较BUILD_TYPE
判断是不是测试构建。
我们可以在业务代码中访问这些field,用于为不同环境的构建产生不同的逻辑。例如我这里在代码中判断BuildConfig.DEBUG
的值是否为true
,如果为true
则对应debug
构建,因此会为Column
中的TitleBar
添加一个用于产生测试数据的点击事件。
Column(modifier = Modifier.padding(16.dp)) {
// 在测试包中为TitleBar设置点击事件,用于快速产生测试数据
if (BuildConfig.DEBUG) {
TitleBar(
modifier = Modifier.clickable {
for (num in 1..15) {
unfinishedTodos.add(0, Todo(num.toString(), false))
}
}
)} else {
TitleBar()
}
// ...
)
3. 实现原理 🔗
在实现了使用BuildConfig
之后,我们不禁有一个问题,这个类似于条件编译的效果是怎么实现的。首先要确认的一点是,Gradle中的buildConfig
构建特性严格来说部署于条件编译,只是通过自动生成一个BuildConfig
类为用户代码提供了一个获取构建信息的渠道,用户通过分支语句可以为不同的构建环境实现不同的逻辑。然而由于编译器存在编译优化,会把在编译期就可以确定的常量分支优化掉,例如下面的代码可以被直接优化为没有携带分支的语句:
if (true) { // 总是被执行
A()
B()
C()
} else { // 不可能被执行
D()
E()
}
// 优化后的代码
A()
B()
C()
因此,Gradle虽然没有实现严格意义上的条件编译,但是已经达到了条件编译的效果。对我们上面写的示例来说,在debug构建时它会被优化编译为下面的语句:
Column(modifier = Modifier.padding(16.dp)) {
// 在测试包中为TitleBar设置点击事件,用于快速产生测试数据
TitleBar(
modifier = Modifier.clickable {
for (num in 1..15) {
unfinishedTodos.add(0, Todo(num.toString(), false))
}
}
)}
// ...
)
而在release构建时它会被优化为这样的语句:
Column(modifier = Modifier.padding(16.dp)) {
TitleBar()
// ...
)
但是要注意不要把密钥等信息配置成一个buildConfigField
,因为BuildConfig
类文件在大多数情况下也会被打包到APK中,只有当我们用了ProGuard或者R8优化之后才会被移除一些字段。
相关资料 🔗
Gradle tips and recipes | Android Studio | Android Developers