Android 探索之 Task 分析(四)

本文将会分析如下几个task

generateReleaseBuildConfig
prepareLintJar
mainApkListPersistenceRelease
generateReleaseResValues
generateReleaseResources
mergeReleaseResources

7.generateReleaseBuildConfig

该task主要作用是生成BuildConfig文件,主要代码逻辑是,配置阶段代码GenerateBuildConfig.ConfigAction->execute(),主要就是读取gradle文件里面的一些信息(appPackageName、versionName、versionCode等)

源码分析

//com.android.build.gradle.tasks.GenerateBuildConfig
 
  @Override
    public void execute(@NonNull GenerateBuildConfig generateBuildConfigTask) {
        BaseVariantData variantData = scope.getVariantData();

        variantData.generateBuildConfigTask = generateBuildConfigTask;

        final GradleVariantConfiguration variantConfiguration =
                variantData.getVariantConfiguration();

        generateBuildConfigTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
        generateBuildConfigTask.setVariantName(scope.getVariantConfiguration().getFullName());

        generateBuildConfigTask.buildConfigPackageName =
                TaskInputHelper.memoize(variantConfiguration::getOriginalApplicationId);

        generateBuildConfigTask.appPackageName =
                TaskInputHelper.memoize(variantConfiguration::getApplicationId);

        generateBuildConfigTask.versionName =
                TaskInputHelper.memoize(variantConfiguration::getVersionName);
        generateBuildConfigTask.versionCode =
                TaskInputHelper.memoize(variantConfiguration::getVersionCode);

        generateBuildConfigTask.debuggable =
                TaskInputHelper.memoize(
                        () -> variantConfiguration.getBuildType().isDebuggable());

        generateBuildConfigTask.buildTypeName = variantConfiguration.getBuildType().getName();
        
        // no need to memoize, variant configuration does that already.
        generateBuildConfigTask.flavorName = variantConfiguration::getFlavorName;
        // 获取flavorDimensions
        generateBuildConfigTask.flavorNamesWithDimensionNames =
                TaskInputHelper.memoize(variantConfiguration::getFlavorNamesWithDimensionNames);
        // 获取自定义的buildConfigField
        generateBuildConfigTask.items =
                TaskInputHelper.memoize(variantConfiguration::getBuildConfigItems);
 
        generateBuildConfigTask.setSourceOutputDir(scope.getBuildConfigSourceOutputDir());
    }

执行阶段,根据配置阶段读取的gradle里面关于module的信息,然后生成BuildConfig文件。

@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);
        // 1、BuildConfig文件生成器
        BuildConfigGenerator generator = new BuildConfigGenerator(
                getSourceOutputDir(),
                getBuildConfigPackageName());

        // Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
        // from the data flow inspection, so use a non-constant value. However, that defeats
        // the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
        // be completely removed by the compiler), so as a hack we do it only for the case
        // where debug is true, which is the most likely scenario while the user is looking
        // at source code.
        //map.put(PH_DEBUG, Boolean.toString(mDebug));
        // 2、生成config文件的固定信息(DEBUG、APPLICATION_ID、BUILD_TYPE、FLAVOR、VERSION_CODE、VERSION_NAME)
        generator
                .addField(
                        "boolean",
                        "DEBUG",
                        isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
                .addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"')
                .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());
        // 得到flavors 数组,然后生成响应的属性到BuildConfig文件中
        List 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) + '"');
            }
        }

        generator.generate();
    }

根据代码和输出信息,清楚的知道此task是读取gradle里面的config信息,然后生成BuildConfig.java文件。生成信息类似如下

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        generateReleaseBuildConfig 
group:       null 
description: null
conv:        [:]
inputs:
outputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/source/buildConfig/release
<------task end -------  

生成文件为BuildConfig.java

8.prepareLintJar

准备lint,拷贝 lint jar 包到指定位置,由于lint开启时,需要手动指定 lint的jar包,此任务就是复制对应的lint jar包到特定目录下

源码分析

// com.android.build.gradle.internal.tasks.PrepareLintJar
    @TaskAction
    public void prepare() throws IOException {
        // there could be more than one files if the dependency is on a sub-projects that
        // publishes its compile dependencies. Rather than query getSingleFile and fail with
        // a weird message, do a manual check
        Set<File> files = lintChecks.getFiles();
        if (files.size() > 1) {
            throw new RuntimeException(
                    "Found more than one jar in the '"
                            + VariantDependencies.CONFIG_NAME_LINTCHECKS
                            + "' configuration. Only one file is supported. If using a separate Gradle project, make sure compilation dependencies are using compileOnly");
        }

        if (files.isEmpty()) {
            if (outputLintJar.isFile()) {
                FileUtils.delete(outputLintJar);
            }
        } else {
            FileUtils.mkdirs(outputLintJar.getParentFile());
            Files.copy(Iterables.getOnlyElement(files), outputLintJar);
        }
    }

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        prepareLintJar 
group:       null 
description: null
conv:        [:]
inputs:
outputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/lint/lint.jar
<------task end -------  

9.mainApkListPersistenceRelease

将 apkData列表内容写入 apk-list.json

源码分析

//   com.android.build.gradle.tasks.MainApkListPersistence

 @TaskAction
    fun fullTaskAction() {

        FileUtils.deleteIfExists(outputFile)
        val apkDataList = ExistingBuildElements.persistApkList(apkData)
        FileUtils.createFile(outputFile, apkDataList)
    }

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        mainApkListPersistenceRelease 
group:       null 
description: null
conv:        [:]
inputs:
outputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/splits-support/release/apk-list/apk-list.gson
<------task end -------  

variant中的apkDatas

json内容类似如下

[
    {
        "type": "FULL_SPLIT",
        "splits": [],
        "versionCode": 1,
        "versionName": "1.0",
        "enabled": true,
        "filterName": "universal",
        "outputFile": "app-universal-release-unsigned.apk",
        "fullName": "universalRelease",
        "baseName": "universal-release"
    },
    {
        "type": "FULL_SPLIT",
        "splits": [
            {
                "filterType": "DENSITY",
                "value": "mdpi"
            }
        ],
        "versionCode": 1,
        "versionName": "1.0",
        "enabled": true,
        "filterName": "mdpi",
        "outputFile": "app-mdpi-release-unsigned.apk",
        "fullName": "mdpiRelease",
        "baseName": "mdpi-release"
    },
    {
        "type": "FULL_SPLIT",
        "splits": [
            {
                "filterType": "DENSITY",
                "value": "hdpi"
            }
        ],
        "versionCode": 1,
        "versionName": "1.0",
        "enabled": true,
        "filterName": "hdpi",
        "outputFile": "app-hdpi-release-unsigned.apk",
        "fullName": "hdpiRelease",
        "baseName": "hdpi-release"
    },
    {
        "type": "FULL_SPLIT",
        "splits": [
            {
                "filterType": "DENSITY",
                "value": "xhdpi"
            }
        ],
        "versionCode": 1,
        "versionName": "1.0",
        "enabled": true,
        "filterName": "xhdpi",
        "outputFile": "app-xhdpi-release-unsigned.apk",
        "fullName": "xhdpiRelease",
        "baseName": "xhdpi-release"
    }
]

10.generateReleaseResValues

该任务就是把我们在gradle里面配置的resValue读取到,然后在app/build/generated/res/resValues/release目录下生成generate.xml文件,该文件里面的内容,可直接在xml文件和代码中使用,方便做一些动态化的配置。

源码分析

     // com.android.build.gradle.tasks.GenerateResValues
       @Override
        public void execute(@NonNull GenerateResValues generateResValuesTask) {
            scope.getVariantData().generateResValuesTask = generateResValuesTask;

            final GradleVariantConfiguration variantConfiguration =
                    scope.getVariantData().getVariantConfiguration();

            generateResValuesTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
            generateResValuesTask.setVariantName(variantConfiguration.getFullName());

            generateResValuesTask.items =
                    TaskInputHelper.memoize(variantConfiguration::getResValues);

            generateResValuesTask.setResOutputDir(scope.getGeneratedResOutputDir());
        }

// 主要生成逻辑
    @TaskAction
    void generate() throws IOException, ParserConfigurationException {
        // 1、输出路径,就是我们output中的目录
        File folder = getResOutputDir();
        // 2、这里的getItems()就是获得我们在代码中设置的resValues
        List<Object> resolvedItems = getItems();

        if (resolvedItems.isEmpty()) {
            FileUtils.cleanOutputDir(folder);
        } else {
            // 3、在resolvedItems不等于空的时候,通过ResValueGenerator生成我们的generated.xml文件
            ResValueGenerator generator = new ResValueGenerator(folder);
            generator.addItems(getItems());
            generator.generate();
        }
    }

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        generateReleaseResValues 
group:       null 
description: null
conv:        [:]
inputs:
outputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/resValues/release
<------task end -------

使用方法示例

//1 .在build.gradle 中增加如下代码,resValue
 buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // 添加代码
            resValue "string", "AppName", "app_release"
        }
        // 添加代码
        debug {
            resValue "string", "AppName", "app_debug"
        }
    }
//2.执行过generateReleaseResValues task以后,会在app/build/generated/res/resValues/release目录下生成一个generated.xml文件
//文件内容类似如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->

    <!-- Values from build type: release -->
    <string name="AppName" translatable="false">app_release</string>

</resources>

//3.生成的内容我们可以直接在xml中使用(android:text="@string/AppName"),也可以在代码中使用(getResources().getString(R.string.AppName);)。
// 也可以通过product Flavor 配置

11.generateReleaseResources

该task为锚点task,保证此前的task执行完以后,才会执行后续的task,为后续的Resource处理做准备

源码分析

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        generateReleaseResources 
group:       null 
description: null
conv:        [:]
inputs:
outputs:
<------task end -------  

12.mergeReleaseResources

mergeReleaseResources 的任务是把依赖的库和工程中的资源进行merge操作。

源码分析

//com.android.build.gradle.tasks.MergeResources

//主要逻辑代码MergeResources.java->doFullTaskAction()方法
@Override
    protected void doFullTaskAction() throws IOException, JAXBException {
        // 1、得到ResourcePreprocessor子类对象
        ResourcePreprocessor preprocessor = getPreprocessor();
        // 2、得到task的output目录
        // this is full run, clean the previous outputs
        File destinationDir = getOutputDir();
        FileUtils.cleanOutputDir(destinationDir);
        if (dataBindingLayoutInfoOutFolder != null) {
            FileUtils.deleteDirectoryContents(dataBindingLayoutInfoOutFolder);
        }
        // 3、得到inputs文件目录集合
        List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
        // 4、生成ResourceMerger 对象
        // create a new merger and populate it with the sets.
        ResourceMerger merger = new ResourceMerger(minSdk);
        MergingLog mergingLog = null;
        // 5、mergeingLog 记录
        if (blameLogFolder != null) {
            FileUtils.cleanOutputDir(blameLogFolder);
            mergingLog = new MergingLog(blameLogFolder);
        }
        // 6、resourceCompiler,实际是Appt工具对象
        try (QueueableResourceCompiler resourceCompiler =
                getResourceProcessor(
                        aaptGeneration,
                        getBuilder(),
                        crunchPng,
                        variantScope,
                        getAaptTempDir(),
                        mergingLog,
                        flags,
                        processResources)) {

            for (ResourceSet resourceSet : resourceSets) {
                resourceSet.loadFromFiles(getILogger());
                merger.addDataSet(resourceSet);
            }

            MergedResourceWriter writer =
                    new MergedResourceWriter(
                            workerExecutorFacade,
                            destinationDir,
                            getPublicFile(),
                            mergingLog,
                            preprocessor,
                            resourceCompiler,
                            getIncrementalFolder(),
                            dataBindingLayoutProcessor,
                            mergedNotCompiledResourcesOutputDirectory,
                            pseudoLocalesEnabled,
                            getCrunchPng());
            // 7、执行merge resource 操作
            merger.mergeData(writer, false /*doCleanUp*/);

            if (dataBindingLayoutProcessor != null) {
                dataBindingLayoutProcessor.end();
            }

            // No exception? Write the known state.
            merger.writeBlobTo(getIncrementalFolder(), writer, false);
        } catch (MergingException e) {
            System.out.println(e.getMessage());
            merger.cleanBlob(getIncrementalFolder());
            throw new ResourceException(e.getMessage(), e);
        } finally {
            cleanup();
        }
    }

第一步:得到ResourcePreprocessor子类对象,实际是MergeResourcesVectorDrawableRenderer对象,该类又继承了VectorDrawableRenderer,该类是通过VectorDrawable文件生产PNG图片,或者拷贝 xml文件。
第二步:得到task的output目录,实际上就是/app/build/intermediates/res/merged/release目录;
第三步:得到inputs文件目录集合,调用getConfiguredResourceSets(preprocessor)方法,该方法就是得到inputs路径;
第四步:生成ResourceMerger 对象。该类实现了DataMerger抽象类,主要的merge逻辑均在此类里面实现 ,类名com.android.ide.common.res2.ResourceMerger
第五步:mergeingLog 记录,就是intermediates/blame/res/release目录,记录操作日志。
第六步:resourceCompiler,实际是Appt工具对象。
第七步:执行merge resource 操作

输入和输出

-----task begin-------->
project:     project ':app_driver' 
name:        mergeReleaseResources 
group:       null 
description: null
conv:        [:]
inputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/resValues/release
 /Users/dongkai/Code/XiWeiLogistics/ymm_app_driver_main_module/build/intermediates/packaged_res/release
 /Users/dongkai/Code/XiWeiLogistics/biz_common/build/intermediates/packaged_res/release
 /Users/dongkai/.gradle/caches/transforms-1/files-1.1/activity_result_util-1.0.1.aar/4b6d13d6fe7210d069c29939ba8fea95/res
  ...
  ...
  ...
 /Users/dongkai/.gradle/caches/transforms-1/files-1.1/sdk-2.13.3.0.aar/cbde3b805a388d5cd0d41d9ac1be0c66/res
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/rs/release
 /Users/dongkai/Code/XiWeiLogistics/app_driver/src/release/res
 /Users/dongkai/Code/XiWeiLogistics/app_driver/src/main/res
outputs:
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/blame/res/release
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/pngs/release
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/incremental/mergeReleaseResources
 /Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/res/merged/release
<------task end -------

输入文件路径,大致有这几种:

  • generateReleaseResValues 任务(上一步)生成的resValues文件(generated/res/resValues/release/generated.xml);
  • 引用的aar包里面的资源(appcompat-v7-26.1.0.aar/a7cc521b4567369eba0ddb355f44a660/res,/sdk-2.13.3.0.aar/cbde3b805a388d5cd0d41d9ac1be0c66/res,第三方的res);
  • compileReleasegRenderscript 任务生的Renderscript文件(generated/res/rs/release);
  • 项目中的res资源文件(/app/src/release/res、/app/src/main/res);

输入文件经过mergeReleaseResources任务处理后,生产的输出文件有如下几种:

  • merge操作的日志记录(intermediates/blame/res/release);
  • png图片集合(generated/res/pngs/release);
  • merge后的资源集合(incremental/mergeReleaseResources);
  • 资源映射关系集合(intermediates/res/merged/release);

这年头生活不易,人生不息,折腾不止!!!!

发表评论