背景
观察一些 Gradle 最糟糕的做法,看看如何用最佳做法替换它们,并以 CPU 配置文件和构建扫描作为指南。学习如何创建构建过程的 CPU 配置文件并学习一些关于解释它们的知识。
Build Scan
查看项目的配置性能:
虽然构建扫描足以表明正在发生的事情,但它并没有提供足够的细节来理解那是什么。什么花了这么多时间?为什么要创建这些任务?我以为我专门使用了任务配置避免......
val adviceTasks = tasks.withType<AdviceTask>()
val dependencyOutputs = mutableListOf<RegularFileProperty>()
adviceTasks.all {
dependencyOutputs.add(adviceReport)
}
val aggregateAdviceTask =
tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice") {
// a ListProperty<RegularFileProperty>
dependencyAdvice.set(dependencyOutputs)
}
这里的问题是 tasks.withType().all { ... },它急切地实现了 AdviceTask 类型的所有任务,从而克服了配置规避。
val aggregateAdviceTask
= tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice")
val adviceTask = ...
aggregateAdviceTask.configure {
// a ListProperty<RegularFile>
dependencyAdvice.add(adviceTask.map { it.adviceReport })
}
不同之处在于我立即注册了聚合任务,没有配置块。然后我在每次添加“子”或特定于变体的任务时对其进行零碎配置。
CPU 配置文件
AndroidAppAnalyzer 是负责分析 com.android.application 项目的代码。它负责 50% 的 CPU 使用率!从配置文件中,我可以看出这是由于 registerClassAnalysisTask() 造成的,这反过来又需要一些 - 显然 - 相当昂贵的计算。这是有问题的代码:
val javaVariantFiles = androidSourceSets.flatMapToSet { sourceSet ->
project.files(sourceSet.javaDirectories)
.asFileTree
.files
.toVariantFiles(sourceSet.name)
}
此代码执行以下操作:
迭代给定变体的 Android 源集(一个 Set<SourceProvider>)
获取每个源集的 Java 目录
将每个目录转换为 FileTree(指向文件而不是目录)
将该文件树作为 Set<File> 获取
将它们转换为名为 VariantFile 的自定义数据类,定义如下:
/**
* Associates a [variant] ("main", "debug", "release", ...) with a
* [filePath] (to a file such as Java, Kotlin, or XML).
*/
data class VariantFile(
val variant: String,
val filePath: String
)
最后两个步骤(.files 和 .toVariantFiles())是昂贵的部分,分别占总成本的 35% 和 10%。
我能够通过添加一个完成这项工作的新任务(将工作从配置中推送到可缓存任务中)来找到这个问题的解决方案,并将新任务的输出作为我现在便宜得多的输入配置 ClassListAnalysisTask。
总 CPU 时间的 50% 到 7% 以下!不错。配置时间也从超过 6 秒减少到刚刚超过 3 秒。
使用 Gradle-Profiler 分析构建
如何分析构建的配置阶段:
$ gradle-profiler —profile async-profiler --project-dir <root-dir-of-build> help