一、学习目标
BuildConfig 类是 Gradle 自动生成的,存放在 /build/generated/source/buildConfig/debug(release)/包名/BuildConfig 因此我们需要在 Gradle 构建时来配置这个类的属性。而在开发中,我们想在
BuildConfig
中增加一个
DEBUG_LOG
属性,用来判断是否开启 log 输出日志的开关。下面我们来实现一个小功能,并且分析这个类是如何被生成的。
-
1、通过两种方式来为 BuildConfig 添加常量属性。
-
2、通过源码分析 BuildConfig 是如何生成。
二、添加 BuildConfig 常量属性
我们想要 Gradle 自动帮我们生成的 BuildConfig 中添加多一个常量值 DEBUG_LOG。具体生成的最终代码如下所示:
/**
* Automatically generated file. DO NOT MODIFY
*/
package com.exampke.buildconfig;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.example.buildconfig";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Fields from default config.
public static final boolean DEBUG_LOG = true;
}
使用 BuildConfig.DEBUG_LOG
//判断是否开启 log 日志输出
boolean debugLog = BuildConfig.DEBUG_LOG;
2.1 方式一
- 方式一:通过 buildConfigField 配置 BuildConfig 属性
通过 build.gradle 配置 buildConfigField 属性,然后点击 sync 就可以生成对应的 BuildConfig 类。
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.buildconfig"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//配置 Config 选项
buildConfigField "boolean", "DEBUG_LOG", true.toString()
}
}
2.2 方式二
- 方式二 通过给 GenerateBuildConfig 添加数据
通过给 GenerateBuildConfig 添加数据,然后点击 sync 就可以生成对应的 BuildConfig 类。
getApplicationVariants().all {
//得到变体
variant ->
//得到生成 BuildConfig 的 GenerateBuildConfig
def buildConfigTask = variant.getGenerateBuildConfig() as Task
buildConfigTask.doFirst {
//在 task 执行之前,往 items 集合中添加你想要的数据,对应的数据是封装在 ClassFieldImpl 中
items.add(new ClassFieldImpl("String", "DEBUG_LOG", true.toString()))
}
}
上面两种方式最终都是通过 GenerateBuildConfig 这个 Task 去生成这个 BuildConfig 文件。下面我们通过源码的角度来分析一下这个类是如何生成的。
三、通过源码分析BuildConfig是如何生成
3.1 给Variant的BuildConfig添加常量属性
我们都知道,对于每一个变体
Variant
来说,都可以通过
getGenerateBuildConfig()
来获取生成 BuildConfig 类的 Task 对象,并且在这个 Task 执行之前,往内部的集合items添加我们需要生成的常量属性,这样在执行这个 task 时就可以帮我们生成我们自定义的常量属性了。
getApplicationVariants().all {
//得到变体
variant ->
//得到生成 BuildConfig 的 GenerateBuildConfig
def buildConfigTask = variant.getGenerateBuildConfig() as Task
buildConfigTask.doFirst {
//在 task 执行之前,往 items 集合中添加你想要的数据,对应的数据是封装在 ClassFieldImpl 中
items.add(new ClassFieldImpl("String", "DEBUG_LOG", true.toString()))
}
}
//BaseVariantImpl
@Override
public GenerateBuildConfig getGenerateBuildConfig() {
return getVariantData().generateBuildConfigTask;
}
3.2 GenerateBuildConfig
GenerateBuildConfig
是一个
Task
,因此我们只需要关注这个类执行的
入口
,每一个 Task 内部使用
@TaskAction
注解标识方法就这个 Task 执行的入口。
GenerateBuildConfig
中的
GenerateBuildConfig#generate()
方法就是我们所说程序执行的入口。
public class GenerateBuildConfig extends BaseTask {
@TaskAction
void generate() throws IOException {
...
}
}
3.3 执行 Task
关于
BuildConfig
类的名字,输出路径,包名以及对应的需要添加的常量属性都会被封装到
BuildConfigGenerator
这个类中。
下面的代码是给通过
addField()
来添加默认的常量属性,例如”DEBUG”,”APPLICATION_ID”等。而我们自定义的一些常量属性则是通过
addItems(getItems())
来添加的。
//GenerateBuildConfig
@TaskAction
void generate() throws IOException {
// must clear the folder in case the packagename changed, otherwise,
// there'll be two classes.
File destinationDir = getSourceOutputDir();
FileUtils.cleanOutputDir(destinationDir);
BuildConfigGenerator generator = new BuildConfigGenerator(
getSourceOutputDir(),
getBuildConfigPackageName());
// 默认添加的一些常量,例如"DEBUG","APPLICATION_ID"等。
generator.addField("boolean", "DEBUG",
isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
.addField("String", "APPLICATION_ID", '"' + getAppPackageName() + '"')
.addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"')
.addField("String", "FLAVOR", '"' + getFlavorName() + '"')
.addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
.addField("String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"')
.addItems(getItems());
List<String> flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i < count; i += 2) {
generator.addField(
"String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"');
}
}
//实际生成 BuildConfig 的入口。
generator.generate();
}
这里
getItems()
得到
items
集合就是用来存放自定义的常量属性的,我们在
方式二
中拿到的
items
就是这个集合了。
//添加我们需要的常量属性
public List<Object> getItems() {
return items;
}
3.4 生成 BuildConfig 文件
在
generate()
内部是使用
JavaPoet
来生成 BuildConfig 文件的。
对于常量属性, mFields 集合是用来存放默认的常量属性,例如”DEBUG”,”APPLICATION_ID”等,而 mItems 集合是用来存放我们自定义的常量属性。
下面是通过是具体的这个类生成的核心代码。而具体的如何使用
JavaWriter
的可以参考一下
官方文档
,这里只是粗略描述一下是如何生成 BuildConfig 这个类的。
//GenerateBuildConfig
/**
* Generates the BuildConfig class.
*/
public void generate() throws IOException {
File pkgFolder = getFolderPath();
if (!pkgFolder.isDirectory()) {
if (!pkgFolder.mkdirs()) {
throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
}
}
File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
Closer closer = Closer.create();
try {
FileOutputStream fos = closer.register(new FileOutputStream(buildConfigJava));
OutputStreamWriter out = closer.register(new OutputStreamWriter(fos, Charsets.UTF_8));
JavaWriter writer = closer.register(new JavaWriter(out));
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(mBuildConfigPackageName)
.beginType("BuildConfig", "class", PUBLIC_FINAL);
//1.生成对应的常量,例如public static final String BUILD_TYPE = "debug";
for (ClassField field : mFields) {
emitClassField(writer, field);
}
for (Object item : mItems) {
if (item instanceof ClassField) {
//2.我们前面在 mItems 集合中添加的数据就是在这里被写入的。
//public static final boolean DEBUG_LOG = true;
//在方式二中,往 items 添加的就是 ClassField 类型的元素
emitClassField(writer, (ClassField) item);
} else if (item instanceof String) {
writer.emitSingleLineComment((String) item);
}
}
writer.endType();
} catch (Throwable e) {
throw closer.rethrow(e);
} finally {
closer.close();
}
}
四、总结
本文主要是简单的描述了 BuildConfig 这个类的使用以及粗略地分析了 Gradle 是如何帮我们生成 BuildConfig 这个类的流程。
记录于 2019年2月21日晚