增量Lint检测实现原理

  • 一.基本思路
  • 二.实现
    • 1.自定义LintClient
    • 2.增量检查文件
    • 3.其他自定义类
    • 4.自定义Gradle插件

在Lint实现原理里已经知道,Lint检测的文件,默认是Project的javaSourceFolders和resourceFolders,但是这样会造成每次Lint检测的时间很长,我们pipeline的效率就很低;所以我们设想要做到一种增量检查:每次只检查改动的文件

一.基本思路

我们先回顾一下获取要检测文件的方式:

val files = project.subset
if (files != null) {checkIndividualJavaFiles(project, main, checks, files)
} else {val sourceFolders = project.javaSourceFolderscheckJava(project, main, sourceFolders, testFolders, generatedFolders, checks)
}

可以发现,在取Project的代码目录前,会优先判断其subset这个List有没有值,有的话,就只检测这些文件,所以我们可以得出结论:将我们改动的文件,放到Project的subset字段里即可
在此,我们需要实现一个自定义gradle插件来实现这一套功能

二.实现

1.自定义LintClient

那Project在哪里可以获取到呢?这里要注意,检测时使用的Project,是Lint里的com.android.tools.lint.detector.api.Project,不是gradle的Project:

@Override
protected LintRequest createLintRequest(@NonNull List<File> files) {LintRequest lintRequest = new LintRequest(this, files);LintGradleProject.ProjectSearch search = new LintGradleProject.ProjectSearch();Project project =search.getProject(this, gradleProject, variant != null ? variant.getName() : null);lintRequest.setProjects(Collections.singletonList(project));//...return lintRequest;
}

往上倒可以发现,Project是在创建LintRequest时创建的,只有拿到LintRequest,才可以获取Project
LintCliClient:

public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {//...LintRequest lintRequest = createLintRequest(files);
}

可以发现,createLintRequest()方法是LintCliClient的一个重写方法,而LintCliClient在Lint里的实现是LintGradleClient类,那么我们可以继承LintGradleClient写一个类,重写createLintRequest()方法,就可以拿到LintRequest对象了:

public class IncrementLintClient extends LintGradleClient {private final org.gradle.api.Project gradleProject;private IncrementLintExtension extension;public IncrementLintClient(...) {super(version, registry, flags, gradleProject, sdkHome, variant, variantInputs, buildToolInfoRevision, isAndroid, baselineVariantName);this.gradleProject = gradleProject;this.extension = extension;}@Overrideprotected LintRequest createLintRequest(List<File> files) {LintRequest lintRequest = super.createLintRequest(files);if (lintRequest != null) {Collection<Project> projects = lintRequest.getProjects();if (projects != null) {for (Project project : projects) {try {addFiles(project);} catch (IOException e) {e.printStackTrace();}}}}return lintRequest;}
}

2.增量检查文件

拿到LintRequest,从而拿到Project,就可以向其添加待检测的File对象,那么这些文件我们如何获取呢?
我们可以写一个本地文本文件,以某种方式在Lint检查开始前,将待检测的文件路径写入到这个文件,然后在添加subset时,读取这个文件里的内容就好:

那么这些文件路径怎么获取呢,这里可以简单举个例子:比如我们是在提交了一个MR,跑pipeline时进行Lint检查时,我们可以添加另一个Task,这个Task是请求MR的相关api,获取此次MR的diff改动文件路径,然后写入到commitFiles.txt这个文本文件即可,当然要记得让Lint的Task依赖于这个Task先执行
有了这个文本文件,我们首先应该在gradle插件的extension里指定一个变量,设置这个文件的名字:

open class IncrementLintExtension {var recordPath: String? = ''
}

这样我们可以在配置插件时设置:

apply plugin: 'com.android.incrementlint'
IncrementLint {recordPath = 'commitFiles.txt'
}

然后我们添加subset:

private void addFiles(Project project) throws IOException {File file = new File(gradleProject.getRootDir(), extension.getRecordPath());if (file.exists()) {FileInputStream inputStream = null;BufferedReader bufferedReader = null;try {inputStream = new FileInputStream(file);bufferedReader = new BufferedReader(new InputStreamReader(inputStream));//读取文本文件String lineStr;while ((lineStr = bufferedReader.readLine()) != null) {//将File添加到project的subsetFile commitFile = new File(lineStr);if (commitFile.exists() && shouldCheck(commitFile)) {project.addFile(commitFile);}}//如果一个增量文件都没有,添加一个占位的文件防止走全量lint检查if (project.getSubset() == null || project.getSubset().isEmpty()) {project.addFile(new File("NotLintCheckStub.file"));}}...}//manifest的特殊处理List<File> manifestFiles = new ArrayList<>();if (project.getSubset() != null && !project.getSubset().isEmpty()) {for (File f : project.getSubset()) {if (f.exists() && SdkConstants.ANDROID_MANIFEST_XML.equals(f.getName())) {manifestFiles.add(f);}}}if (!manifestFiles.isEmpty()) {Field field;try {field = Project.class.getDeclaredField("manifestFiles");field.setAccessible(true);field.set(project, manifestFiles);}...}
}

这里的逻辑很简单,就是读取文本文件,每一行是一个File对象,添加到subset中即可
这里有一个针对AndroidManifest.xml的特殊处理:把要检测的AndroidManifest.xml文件,通过反射放到Project的manifestFiles字段,才可以检测到这些文件
这样的原因是在LintDriver进行检测时,会调用的project.getManifestFiles()方法获取默认的AndroidManifest.xml文件进行检测:

for (manifestFile in project.manifestFiles) {//...
}@Override
public List<File> getManifestFiles() {if (manifestFiles == null) {manifestFiles = Lists.newArrayList();//每个variant的AndroidManifest.xmlfor (SourceProvider provider : getSourceProviders()) {File manifestFile = provider.getManifestFile();if (manifestFile.exists()) {manifestFiles.add(manifestFile);}}}return manifestFiles;
}

而在project文件夹外的AndroidManifest.xml文件就不会被检测,所以需要手动通过反射将我们要检测的AndroidManifest.xml设置到manifestFiles这个字段里

3.其他自定义类

实现了自定义的LintClient,还需要看原生Lint是如何构建LintGradleClient的
LintGradleExecution:

private Pair<List<Warning>, LintBaseline> runLint(...) {LintGradleClient client = new LintGradleClient(...);
}

是一个private方法,直接new了LintGradleClient,而我们是继承自LintGradleClient的子类,所以需要copy一份LintGradleExecution:

private Pair<List<Warning>, LintBaseline> runLint(...) {IncrementLintClient client = new IncrementLintClient(...);
}

再看LintGradleExecution是怎么被调用的
ReflectiveLintRunner:

fun runLint(...) {try {val loader = getLintClassLoader(gradle, lintClassPath)val cls = loader.loadClass("com.android.tools.lint.gradle.LintGradleExecution")val constructor = cls.getConstructor(LintExecutionRequest::class.java)val driver = constructor.newInstance(request)val analyzeMethod = driver.javaClass.getDeclaredMethod("analyze")analyzeMethod.invoke(driver)}...
}

是通过被反射调用的,而且runLint方法不能被重写,所以我们也需要copy一份LintRunner进行改动:

void runLint(...) {try {//...Class cls = loader.loadClass("com.android.incrementlint.IncrementLintGradleExecution");//...Method analyzeMethod = driver.getClass().getDeclaredMethod("analyze");analyzeMethod.invoke(driver);}...
}

最后再来看ReflectiveLintRunner怎么被调用的
LintBaseTask:

protected void runLint(LintBaseTaskDescriptor descriptor) {FileCollection lintClassPath = getLintClassPath();if (lintClassPath != null) {new ReflectiveLintRunner().runLint(getProject().getGradle(),descriptor, lintClassPath.getFiles());}
}

我们需要继承自LintBaseTask实现一个自定义的LintTask,重写runLint方法:

protected void runLint(LintBaseTaskDescriptor descriptor) {FileCollection lintClassPath = getLintClassPath();if (lintClassPath != null) {new LintRunner().runLint(getProject().getGradle(), descriptor, lintClassPath.getFiles(), extension);}}

4.自定义Gradle插件

有了自定义的Task、extension,就可以通过gradle plugin将其包装,为每个variant添加LintTask,形成一个sdk了

public class IncrementLintPlugin implements Plugin<Project> {public final static String EXTENSION_NAME = "IncrementLint";@Overridepublic void apply(Project project) {IncrementLintExtension incrementLintExtension = project.getExtensions().create(EXTENSION_NAME, IncrementLintExtension.class);project.afterEvaluate(p -> {//为application module添加LintTaskAppExtension appExtension = p.getExtensions().findByType(AppExtension.class);if (appExtension != null) {DomainObjectSet<ApplicationVariant> variants = appExtension.getApplicationVariants();for (ApplicationVariant variant : variants) {if (variant instanceof ApplicationVariantImpl) {ApplicationVariantImpl variantImpl = (ApplicationVariantImpl) variant;VariantScope globalScope = variantImpl.getVariantData().getScope();applyLintTask(project, collectAllFilesTask, incrementLintExtension, globalScope);}}}//为library module添加LintTaskLibraryExtension libraryExtension = p.getExtensions().findByType(LibraryExtension.class);if (libraryExtension != null) {DefaultDomainObjectSet<LibraryVariant> variants = libraryExtension.getLibraryVariants();for (LibraryVariant variant : variants) {if (variant instanceof LibraryVariantImpl) {LibraryVariantImpl variantImpl = (LibraryVariantImpl) variant;LibraryVariantData libraryVariantData = getVariantData(variantImpl);if (libraryVariantData != null) {VariantScope globalScope = libraryVariantData.getScope();applyLintTask(project, collectAllFilesTask, incrementLintExtension, globalScope);}}}}});}private void applyLintTask(Project project, Task collectAllFilesTask, IncrementLintExtension incrementLintExtension, VariantScope globalScope) {LintTask lintTask = project.getTasks().create(globalScope.getTaskName(LintTask.NAME), LintTask.class);new LintTask.VitalCreationAction(globalScope, null, project, incrementLintExtension).configure(lintTask);}
}

gradle插件相关可以参考Gradle及插件使用

增量Lint检测实现原理相关推荐

  1. iOS 增量代码覆盖率检测实践

    总第321篇 2018年 第113篇 本文介绍了对iOS覆盖率检测算法的研究,分享一种可以嵌入到现有开发流程中,并对开发透明的增量代码测试覆盖率工具的实现. 到店餐饮技术部交易与信息技术中心,负责点评 ...

  2. 三维目标检测算法原理

    三维目标检测算法原理 输入输出接口 Input: (1)图像视频分辨率(整型int) (2)图像视频格式(RGB,YUV,MP4等) (3)左右两边的车道线位置信息摄像头标定参数(中心位置(x,y) ...

  3. 利用霍夫梯度法进行圆检测的原理概要及OpenCV代码实现

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 霍夫圆变换的基本原理与霍夫线变换原理类似,对直线 ...

  4. LSD快速直线检测的原理概要及OpenCV代码实现(CV类LineSegmentDetector)

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 LSD快速直线检测算法是由Rafael Grom ...

  5. 信安教程第二版-第10章入侵检测技术原理与应用

    第10章 入侵检测技术原理与应用 10.1 入侵检测概述 193 10.1.1 入侵检测概念 193 10.1.2 入侵检测模型 193 10.1.3 入侵检测作用 194 10.2 入侵检测技术 1 ...

  6. 光电整纬机狭缝检测工作原理

    光电整纬机狭缝检测工作原理 传统的织物整纬检测方式采用 2-3 个固定狭缝检测纬斜,每个狭缝只检测固定的纬斜角度.因固定狭缝个数的限制,使得检测精度无法满足实际要求.本课题采用旋转狭缝检测头来对织物纬 ...

  7. 增量式编码器工作原理超详细图解

    旋转编码器是由光栅盘(又叫分度码盘)和光电检测装置(又叫接收器)组成.光栅盘是在一定直径的圆板上等分地开通若干个长方形孔.由于光栅盘与电机同轴,电机旋转时,光栅盘与电机同速旋转,发光二极管垂直照射光栅 ...

  8. SSD目标检测算法原理(上)

    目录 一.目标检测概述 1.1 项目演示介绍 1.2 图片识别背景 1.3 目标检测定义 二.目标检测算法原理 2.1 任务描述 2.2 目标检测算法必备基础 2.3目标检测算法模型输出 目标检测 - ...

  9. 弹性响应蒸馏 | 用弹性响应蒸馏克服增量目标检测中的灾难性遗忘

      欢迎关注我的公众号 [极智视界],获取我的更多笔记分享   大家好,我是极智视界,本文解读一下 用弹性蒸馏克服增量目标检测中的灾难性遗忘.   传统的目标检测不适用于增量学习.然而,仅用新数据直接 ...

最新文章

  1. 《人民日报》发声:科研人员收入理应体现他们的价值
  2. python3 爬淘女郎
  3. boost::copy_n相关的测试程序
  4. ArcGIS实验教程——实验三十八:基于ArcGIS的等高线、山体阴影、山顶点提取案例教程
  5. 48 CO配置-控制-获利能力分析-创建经营组织
  6. 全新骁龙855 Plus加持!ROG游戏手机2下周发布:无惧逆风挑战
  7. python的数据库操作_Python对数据库操作
  8. 一小时精通SVN版本控制 之五 团队协作开发
  9. iotop监视磁盘I/O
  10. 史上最强三千六百道脑筋急转弯(5)
  11. 理论物理考研攻略!!!
  12. Dreamweaver CS6的基本使用教程
  13. LaTeX公式编辑器数学、化学、物理公式编辑器
  14. 教学演示软件 模型十四 三维图象渲染模型
  15. 聚合物-化学键-聚合物PEG-Hyd-PDLLA /PLA-PHis-hyd-PEG/PEG-PUSeSe-PEG
  16. _WIN32_WINNT not defined
  17. 【FAQ】接入HMS Core推送服务过程中一些常见问题总结
  18. 揭秘可变剪切研究的本质
  19. 代理ARP的作用和原理
  20. 计算机怎么设置按音乐,Win7笔记本怎么设置开机音乐?电脑开机音乐如何设置?...

热门文章

  1. 智能工厂设备无人值守系统方案
  2. 李云赫天津大学计算机,祝贺创业谷涌现全国自强之星,同济创业谷,陪伴这个世上最有梦想的人...
  3. awg线径与电流_AWG_线径电流
  4. c语言转义字符总结,C语言转义字符总结
  5. 概率论考点总结类型27 上侧α分位点
  6. 目标跟踪OTB评估指标(OP, CLE, FPS)与matlab代码
  7. 在线答题刷题,创建题库智能组卷,更高效!
  8. 树莓派Pico-Raspberry Pi Pico简介
  9. SpeedTree - 在SpeedTree中导入自定义模型
  10. 马来亚大学研究生多久毕业?这份问卷调查结果拍了拍你