Lint增量扫描实践
1. 背景
在上一篇Android Lint代码检查实践中说到了Lint全量扫描项目的耗时在3.5m,执行时机是在mr的时候,所以在大多数时候,不会因为Lint检查阻塞开发流程。
但是,特殊情况下,比如你只提交了几行代码需要mr的时候,review只需要10秒完事了,而Lint检查却需要3.5m,这个时候你就需要浪费宝贵的3分多钟进行等待,这种事情是我不希望看到的,本着对极致的追求,我决定支持增量扫描的功能,来压缩Lint扫描的时间。
老规矩先看下成果,不然被我骗着做完后发现没卵用怎么办,滑稽脸。
效果是不是很显著,从3分30秒压缩到了30秒左右,23333成功勾起了各位看官的欲望,接下来看看怎么实现的吧。
先来波广告上篇文章中所有功能和本篇的增量扫描都在AndroidLint实现了,欢迎star。
2. 怎么做
2.1 如何找到变更文件
先从简单的入手,对于找到变更文件我们可以通过git diff命令,git diff支持两个分支或者不同commit节点等方式对比修改。我这里的命令是git diff $baseline $revision --name-only --diff-filter=ACMRTUXB
,--name-only
是只展示文件名,--diff-filter=ACMRTUXB
是用来过滤掉删除的文件只要增改的文件,其他的不过多赘述官方文档有详细说明。
2.2 增量扫描思路的形成
变更文件找到了,接下来是做Lint增量扫描,首先想到的肯定是去看LintOptions有没有提供这个功能,很遗憾没有,那也就意味着需要自定义了,但我们并不知道怎么起手,所以就只能先看看AGP提供的LintTask怎么做的。最好情况是他有提供这个功能只是没有开放api给我们,那反射反射程序员的快乐一下就O了,最差的就是得照着他源码自己手撸一套有增量扫描功能的LintTask,这里我直接给出结果,AGP提供的LintTask有这个功能只是没开放api。
接下来我们将debug一遍LintTask执行流程,看看如何开启增量扫描功能,(ps:源码全贴的话特别多不容易抓住重点,所以非重点的就直接展示调用流程了,重点的地方在贴源码)
2.3 Debug Lint Task
LintTask默认有三个实现类
不管哪个都是调到LintBaseTask#runLint()进行lint扫描,具体调用流程如下:
LintBaseTask#runLint()
ReflectiveLintRunner#runLint()
LintGradleExecution#analyze()
LintGradleExecution#runLint()//这里其实跳了一步,三个Task实现类稍有不同但都会调到这
LintGradleClient#run()
LintCliClient#run()
LintDriver#analyze()
LintDriver#checkProject()
LintDriver#runFileDetectors()
我们着重看LintDriver#runFileDetectors()对源码检测的部分
private fun runFileDetectors(project: Project, main: Project?) {
...
if (scope.contains(Scope.JAVA_FILE) || scope.contains(Scope.ALL_JAVA_FILES)) {
val checks = union(
scopeDetectors[Scope.JAVA_FILE],
scopeDetectors[Scope.ALL_JAVA_FILES]
)
if (checks != null && !checks.isEmpty()) {
val files = project.subset
if (files != null) {
//如果project.subset不为空
checkIndividualJavaFiles(project, main, checks, files)//则进行自定义文件的扫描
}
}
}
...
}
可以看到他在project.subset不为null的时候进行自定义文件的扫描,那么我们要做的就是将变更的文件插入到Project中,于是来看Project.subset取的什么
/**
* Adds the given file to the list of files which should be checked in this project. If no files
* are added, the whole project will be checked.
*
* @param file the file to be checked
*/
public void addFile(@NonNull File file) {
if (files == null) {
files = new ArrayList<>();
}
files.add(file);
}
/**
* The list of files to be checked in this project. If null, the whole project should be
* checked.
*
* @return the subset of files to be checked, or null for the whole project
*/
@Nullable
public List<File> getSubset() {
return files;
}
注释我特意没删,上面明确说明了如果getSubset()返回值不为null将只对files中的文件扫描,再次印证了