通过对Task耗时排序,主要的耗时体现在FindBugs和Lint对每一个Module的扫描任务上,CheckStyle任务并不占主要影响。整体来看,除了工具本身的扫描时间外,耗时主要分为多Module、多Variant带来的任务数量耗时。
优化思路分析
对于工具本身的扫描时间,一方面受工具自身扫描算法和检测规则的影响,另一方面也跟扫描的文件数量相关。针对源码类型的工具比如CheckStyle和Lint,需要经过词法分析、语法分析生成抽象语法树,再遍历抽象语法树跟定义的检测规则去匹配;而针对字节码文件的工具FindBugs,需要先编译源码成Class文件,再通过BCEL分析字节码指令并与探测器规则匹配。如果要在工具本身算法上去寻找优化点,代价比较大也不一定能找到有效思路,投入产出比不高,所以我们把精力放在减少Module和Variant带来的影响上。
从上面的耗时分析可以知道,Module和Variant数直接影响任务数量, 一次PR提交的场景是多样的,比如多Module多Variant都有修改,所以要考虑这些都修改的场景。先分析一个Module多Variant的场景,考虑到不同的Variant下源代码有一定差异,并且FindBugs扫描针对的是Class文件,不同的Variant都需要编译后才能扫描,直接对多Variant做处理比较复杂。我们可以简化问题,用以空间换时间的方式,在提交PR的时候根据Variant用不同的Jenkins Job来执行每一个Variant的扫描任务。所以接下来的问题就转变为如何优化在扫描单个Variant的时候多Module任务带来的耗时。
对于Module数而言,我们可以将其抽取成组件,拆分到独立仓库,将扫描任务拆分到各自仓库的变动时期,以aar的形式集成到主项目来减少Module带来的任务数。那对于剩下的Module如何优化呢?无论是哪一种工具,都是对其输入文件进行处理,CheckStyle对Java源代码文件处理,FindBugs对Java字节码文件处理,如果我们可以通过一次任务收集到所有Module的源码文件和编译后的字节码文件,我们就可以减少多Module的任务了。所以对于全量扫描,我们的主要目标是来解决如何一次性收集所有Module的目标文件。
思考三:是否支持增量扫描?
上面的优化思路都是基于全量扫描的,解决的是多Module多Variant带来的任务数量耗时。前面提到,工具本身的扫描时间也跟扫描的文件数量有关,那么是否可以从扫描的文件数量来入手呢?考虑平时的开发场景,提交PR时只是部分文件修改,我们没必要把那些没修改过的存量文件再参与扫描,而只针对修改的增量文件扫描,这样能很大程度降低无效扫描带来的效率问题。有了思路,那么我们考虑以下几个问题:
- 如何收集增量文件,包括源码文件和Class文件?
- 现在业界是否有增量扫描的方案,可行性如何,是否适用我们现状?
- 各个扫描工具如何来支持增量文件的扫描?
根据上面的分析与思考路径,接下来我们详细介绍如何解决上述问题。
全量扫描优化
搜集所有Module目标文件集
获取所有Module目标文件集,首先要找出哪些Module参与了扫描。一个Module工程在Gradle构建系统中被描述为一个“Project”,那么我们只需要找出主工程依赖的所有Project即可。由于依赖配置的多样性,我们可以选择在某些Variant下依赖不同的Module,所以获取参与一次构建时与当前Variant相关的Project对象,我们可以用如下方式:
static Set collectDepProject(Projectproject, BaseVariant variant, Set result = null) {
if (result == null) {
result = new HashSet<>()
}
Set taskSet = variant.javaCompiler.taskDependencies.getDependencies(variant.javaCompiler) taskSet.each { Task task ->
if (task.project != project && hasAndroidPlugin(task.project)) { result.add(task.project)
BaseVariant childVariant = getVariant(task.project)
if (childVariant.name == variant.name || “ v a r i a n t . f l a v o r N a m e {variant.flavorName} variant.flavorName{childVariant.buildType.name}”.toLowerCase() == variant.name.toLowerCase()) {
collectDepProject(task.project, childVariant, result)
}
}
}
returnresult
}
目前文件集分为两类,一类是源码文件,另一类是字节码文件,分别可以如下处理:
projectSet.each { targetProject ->
if (targetProject.plugins.hasPlugin(CodeDetectorPlugin) && GradleUtils.hasAndroidPlugin(targetProject)) {
GradleUtils.getAndro