Android静态代码扫描效率优化与实践,大厂Android开发面试解答

本文探讨了Android应用开发中的静态代码扫描效率优化,重点在于全量和增量扫描策略。通过分析任务耗时,提出减少Module和Variant带来的影响,优化全量扫描时的文件收集和FindBugs的Class文件处理。增量扫描方面,通过git diff获取差异文件,以减少无效扫描。文章还研究了Lint和FindBugs的扫描原理,为实现增量扫描提供了思路。
摘要由CSDN通过智能技术生成

image

通过对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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值