Gradle之android.precompileDependenciesResources配置介绍

本地版用了一个第三方图片压缩插件,用来减少包大小大约2.6兆,但是在gradle升级后,并没有达到预期的效果。之后在gradle.properties中设置了android.precompileDependenciesResources=false后,解决了该问题。android.precompileDependenciesResources是Gradle 3.6版本以后增加的一个配置,并且默认是打开的,但是不知道他具体的作用。我们在开发过程中也很少用到关闭的情况。 所以本次具体介绍一下它的作用,主要从以下几个方面介绍:

  • precompileDependenciesResources的配置
  • MergeResources任务流程解析
  • McImage图片压缩插件问题解决
  • 总结

precompileDependenciesResources在依赖逻辑中的处理


在BooleanOption中可以看到默认设置是true:

1
PRECOMPILE_DEPENDENCIES_RESOURCES("android.precompileDependenciesResources", true, FeatureStage.Supported)

看一下调用的地方,在这里可以看到其实返回是和图片压缩一起判断的,所以这里的注释要注意下:意思是如果使用图片压缩就需要所有的资源都能通过MergeResources任务过程

1
2
3
4
5
6
7
8
9
10
11
/**
* 这里也判断了shrinkResources,我们默认资源缩减是false,所以在不配置android.precompileDependenciesResources=false的情况下,一直返回true,如果
* android.precompileDependenciesResources设置false或者shrinkResources=true,都会返回false。
*/
public boolean isPrecompileDependenciesResourcesEnabled() {
// Resource shrinker expects MergeResources task to have all the resources merged and with
// overlay rules applied, so we have to go through the MergeResources pipeline in case it's
// enabled, see b/134766811.
return globalScope.getProjectOptions().get(BooleanOption.PRECOMPILE_DEPENDENCIES_RESOURCES)
&& !useResourceShrinker();
}

同时在为true的情况下,在依赖处理的逻辑中注册了转化flat的Transform

1
2
3
4
5
6
 if (globalScope.projectOptions[BooleanOption.PRECOMPILE_DEPENDENCIES_RESOURCES]) {
dependencies.registerTransform(
AarResourcesCompilerTransform::class.java
)
......
}

MergeResources


我们先找出mergeReleaseResourcesTask对应的类:根据下面命令可以拿到具体的处理在类MergeResources中。

1
2
3
4
5
6
7
8
9
10
11
chenyulong01deMacBook-Pro:MyActivityTest chenyulong01$ ./gradlew -q help --task app:mergeReleaseResources
Detailed task information for app:mergeReleaseResources
Path
:app:mergeReleaseResources
Type
MergeResources (com.android.build.gradle.tasks.MergeResources)
Description
-
Group
-
chenyulong01deMacBook-Pro:MyActivityTest chenyulong01$

任务执行开始doFullTaskAction方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected void doFullTaskAction() throws IOException, JAXBException {
ResourcePreprocessor preprocessor = getPreprocessor();

// this is full run, clean the previous outputs
File destinationDir = getOutputDir().get().getAsFile();
FileUtils.cleanOutputDir(destinationDir);
if (getDataBindingLayoutInfoOutFolder().isPresent()) {
FileUtils.deleteDirectoryContents(getDataBindingLayoutInfoOutFolder().get().getAsFile());
}

//获取所有的 ResourceSet
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);

// create a new merger and populate it with the sets.
ResourceMerger merger = new ResourceMerger(getMinSdk().get());
.....
try (WorkerExecutorFacade workerExecutorFacade = getAaptWorkerFacade();
ResourceCompilationService resourceCompiler = getResourceProcessor() {
......
//过滤ResourceSet,设置了setAllowedFolderPrefix(FD_RES_VALUES)的非values资源(drawable、layout、。。。。。)不走下面流程
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(new LoggerWrapper(getLogger()));
merger.addDataSet(resourceSet);
}
......
MergedResourceWriter writer = new MergedResourceWriter();
......
merger.mergeData(writer, false /*doCleanUp*/));
.....
}
}

看一下如何获取所有ResourceSet:getConfiguredResourceSets方法里调用了compute方法,这个方法很重要,尤其时注释,说明的很清楚,

1
2
3
private List<ResourceSet> getConfiguredResourceSets(ResourcePreprocessor preprocessor) {
processedInputs = getResourcesComputer().compute(precompileDependenciesResources);
}

compute方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Computes resource sets for merging, if [precompileDependenciesResources] flag is enabled we
* filter out the non-values resources as it's precompiled and is consumed directly in the
* linking step.
* 计算用于合并的资源集,如果启用[PrecompiledDependenciesResources]标志,
* 我们将在预编译时过滤掉非值资源,并在链接步骤中直接使用。
*/
@JvmOverloads
fun compute(precompileDependenciesResources: Boolean = false): List<ResourceSet> {
......
val resourceSetList = ArrayList<ResourceSet>(size)
// 这里要注意,过滤的是libraries库的资源。
addLibraryResources(libraries, resourceSetList, precompileDependenciesResources)
......
return resourceSetList
}

这个时候我们知道在compute处理了非值资源,其实就是非values资源。其实这里不是过滤,是给所有的文件都设置了value标记。
setAllowedFolderPrefix(FD_RES_VALUES),这个标记在读取的时候会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private fun addLibraryResources(
libraries: ArtifactCollection?,
resourceSetList: MutableList<ResourceSet>,
resourceArePrecompiled: Boolean
) {
// add at the beginning since the libraries are less important than the folder based
// resource sets.
// get the dependencies first
libraries?.let {
val libArtifacts = it.artifacts
// the order of the artifact is descending order, so we need to reverse it.
for (artifact in libArtifacts) {
val resourceSet = ResourceSet()
.......
resourceSet.isFromDependency = true
//我们在loadFromFiles处理resourceSet时,会遍历这个set
resourceSet.addSource(artifact.file)

if (resourceArePrecompiled) {
// For values resources we impose stricter rules different from aapt so they need to go
// through the merging step.
// 设置了setAllowedFolderPrefix(FD_RES_VALUES)
resourceSet.setAllowedFolderPrefix(FD_RES_VALUES)
}
// add to 0 always, since we need to reverse the order.
resourceSetList.add(0, resourceSet)
}
}
}

综上其实都是对资源文件的配置,看一下读取,读取开始就是resourceSet.loadFromFiles方法,最近进入readSourceFolder方法,对文件进行处理、过滤,看下面debug图比较清晰,

然后我们看一下,分别设置precompileDependenciesResources后mergerReleaseResources执行完后的/build/res/merged/debug/文件数量对比:

我们发现,未设置false时.flat文件比设置后少了好多,那在processRleaseResources时,这些文件从哪里来呢?肯定不会丢失.我们打印一下processRleaseResources时,task.inputs.files输入文件:执行./gradlew :app:processReleaseResources

precompileDependenciesResources = true

1
2
3
4
5
6
7
8
input file:/Users/chenyulong01/.gradle/caches/transforms-2/files-2.1/5f74d6550e3a0049fba9165d4efc08e0/androidx.appcompat
input file:/Users/chenyulong01/.gradle/caches/transforms-2/files-2.1/2efc557459e42f3d4a45ed4fdeadd8e6/android.support.constraint
input file:/Users/chenyulong01/.gradle/caches/transforms-2/files-2.1/b26cf8ef1b89aa3b84e14c53ba7689f8/androidx.fragment
......
input file:/Users/chenyulong01/.gradle/caches/transforms-2/files-2.1/aa6c769bb172babdd442b10f9ded6c0f/androidx.arch.core
input file:/Users/chenyulong01/.gradle/caches/transforms-2/files-2.1/3d7ded961afb1450efaaf93a54ba3962/androidx.interpolator
input file:/Users/chenyulong01/AndroidStudioProjects/MyActivityTest/app/build/intermediates/res/merged/release
input file:/Users/chenyulong01/AndroidStudioProjects/MyActivityTest/app/build/intermediates/merged_manifests/release

precompileDependenciesResources = false

1
2
input file:/Users/chenyulong01/AndroidStudioProjects/MyActivityTest/app/build/intermediates/res/merged/release
input file:/Users/chenyulong01/AndroidStudioProjects/MyActivityTest/app/build/intermediates/merged_manifests/release

所以,precompileDependenciesResources = true时,libraries的非values资源都是从caches中获取
最后我们看一下gradle 4.0的mergeReleaseResources的Task任务流程:

图片压缩


McImage是无侵入式的全量压缩资源图片插件,包括Jar包中的图、AAR中的图、Module中的图。使用了三种算法:pngquant算法压缩png、guetzli算法压缩jpg、cwebp算法转webp。https://github.com/smallSohoSolo/McImage

Gradle未升级前,由于引入了McImage图片压缩插件,会降低包大小2.6M,但是在升级到4.0.2后,只降低了1.4M。那问题出现在了哪里?为社么会有1.2M左右的差值,当时第一反应就是,压缩失败,插件没有适配Gradle版本。所以在开始就研究了一下McImage的插件源码,然后在工程中直接使用McImage源码。但是根据打印信息来看,跟直接依赖插件输出也完全一样。那么说明插件没有问题。那么到底问题出现在了哪里?

在未解决问题之前,曾想找一个替代插件,看到了https://booster.johnsonlee.io/preface/。 介绍到,Booster是一款专门为移动应用设计的易用、轻量级且可扩展的质量优化框架,其目标主要是为了解决随着APP复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。这里我们只关注包体积相关模块即可。包体积优化,包含资源去冗余、资源压缩等功能模块。
在看到图片压缩时,实现思路是选择在mergeResources和processResources任务之间插入PNG压缩任务,如下图所示:

根据这个思路,查看一下McImage相关源码:源码中有这么一行

1
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))

好吧,源码错了,修改,移动压缩task到mergerResourcesTask后面,结果就是压缩没有问题。但是包大小还是不对。继续往下看booster文档,在看到图片压缩时,看到有这么一个注释:

WARNING
Android Gradle Plugin 3.6 及以上版本,需要在 gradle.properties 中设置:
android.precompileDependenciesResources=false

好吧,尝试一下:结果成功了。那为什么压缩处理的Task是在mergeResources的Task之前执行,跟思路图不同,但是有效呢。我们知道从agp3.0开始,google默认开启了aapt2作为资源编译的编译器,如果是使用aapt,上面的思路就没问题,但是我们已经开启了aapt2编译,那就只能说明上边的思路和当前的的图片压缩思路不一致,不是当前的思路图。因为我们在升级前,agp版本是3.4.3,也没有关闭aapt2,再次证明上述思路针对当前agp版本是错误的。插件代码是没问题的。

agp版本从3.3开始,通过getAllRawAndroidResources获取所有的资源文件,包括来自传递依赖项的资源,举例几个来源:

res/,
app/src/main/res,
app/src/debug/res,
intermediates/packaged_res/debug,
catche/aeec173e09252e6f19e949e00c8c5eec/jetified-XXXX-10010.4.36/res,
build/generated/res/,
build-types/debug/res

BaseVariantImpl:

1
2
3
4
5
6
7
8
9
10
/**
* Returns file collection containing all raw Android resources, including the ones from
* transitive dependencies.
*
* <p><strong>This is an incubating API, and it can be changed or removed without
* notice.</strong>
*/
@Incubating
@NonNull
FileCollection getAllRawAndroidResources();

缓存flat生成


上述说到aar的flat资源不是在mergeResourceTask生成的。但是在processResources中,发现aar的flat来自catch,那缓存中的flat是什么时候生成的呢?在开始将依赖逻辑处理时我们注册了一个AarResourcesCompilerTransform。这个Transform就是处理aar资源flat转换的。所以,当我们在mergeResourceTask之前处理图片的时候,其实aar的资源和图片,都已经转换成了flat。虽然我们压缩没问题,但是我们的flat在压缩之前就完成了转换。所以,我们的包大小并没有减少。也就是1.2M差距的原因。

gradle断点调试


  1. 打开Edit Configurations
  2. 选中Remote,点击+,Remote
  3. 输入Name,拷贝Command line agruments for remote JVM:
  4. 打开gradle.properties
  5. 修改替换org.gradle.jvmargs值为3中拷贝内容。

总结


  • 1.Gradle升级版本差距较大的情况下,有问题首先看源码,多调试,更新文档一般不会提及这种小点。
  • 2.precompileDependenciesResources默认true情况下,默认开启aapt2会减少构建时间。
  • 3.图片压缩插件可指定具体压缩文件,减少构建时间,可以作为优化构建时间的一个优化点。
  • 4.MergerResources是合并生成flat文件。

    参考

    Android构建流程
    Gradle之Android Gradle Plugin 主要 Task分析