Android开发经验谈Android进阶之路Android技术知识

从源码的角度分析Gradle是如何生成 BuildConfig

2019-02-21  本文已影响9人  未见哥哥

一、学习目标

BuildConfig 类是 Gradle 自动生成的,存放在 /build/generated/source/buildConfig/debug(release)/包名/BuildConfig 因此我们需要在 Gradle 构建时来配置这个类的属性。而在开发中,我们想在 BuildConfig 中增加一个 DEBUG_LOG 属性,用来判断是否开启 log 输出日志的开关。下面我们来实现一个小功能,并且分析这个类是如何被生成的。

二、添加 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 方式一

通过 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 添加数据,然后点击 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日晚

上一篇下一篇

猜你喜欢

热点阅读