美文网首页Android开发经验谈Android进阶之路Android技术知识
从源码的角度分析Gradle是如何生成 BuildConfig

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

作者: 未见哥哥 | 来源:发表于2019-02-21 22:05 被阅读9次

    一、学习目标

    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日晚

    相关文章

      网友评论

        本文标题:从源码的角度分析Gradle是如何生成 BuildConfig

        本文链接:https://www.haomeiwen.com/subject/sdrjyqtx.html