自定义lint增量检查

前言

  前一段时间将公司的代码规范文档翻译为lint规则库并在编译时检查。当较小的项目上面运行没有感觉有什么问题,但是当导入较大项目时,就发现编译速度明显变慢,严重影响工作效率,因此就做了lint增量检查。

  废话少说,先上代码IncrementLint,该项目的实现方案是参照Android Lint增量扫描实战纪要,该文档上对实现原理描述得很详细,在此就不再重复,在这里主要想与大家分享完成这个项目时遇到的一些问题和解决方法并介绍一下grale的lintTask是如何接入到原本lint代码的。

问题

  • 问题一:找不到lint检查的入口

  Android Lint增量扫描实战纪要中介绍需要编写LintClinet类继承LintGradleClient,但是LintGradleClient却不知道在哪。其实只要导入"com.android.tools.lint:lint-gradle:26.3.2"这个包,就能找到LintGradleClient这个类。其实最开始我也没找到这个,后来看了sdk工具中lint脚本,然后找到了{sdk}/tools/lib/lint-26.0.0-dev.jar,通过查看这个包里面的源码,看到了LintCliClient这个的使用方法和LintGradleClient很类似,最后通过其包名定位到最后的包。

  这一部分告诉大家一个小技巧(自学gradle,大神绕行), 像com.android.*这些规范的包,其groupId与artifactId与它的包名有关联(例如 LintGradleClient类的包名就是com.android.tools.lint.gradle,com.android.tools.lin是groupId,lint-gradle是artifactId)。

  • 问题二:实例化 LintGradleClient 参数较多不知如何获取

  LintGradleClient继承实现了LintCliClient,在lint-26.0.0-dev.jar中有LintCliClient的初始化和使用,如果定制非gradle版本的lint工具,可以参考这个(gradle版本lint工具忽略)。

  eagleeye-gradle-codescanning中有实例化LintGradleClient所需各个参数的获取方法,但是很多都是使用反射来实现。既然我们要在gradle中执行lint,处于gradle环境中也就没必要需要反射调用了。在 gradle中的lint task执行是由LintBaseTask这里开始执行的,因此查看这部分流程(后面会分析)就能帮助我们完成各个参数,具体请参照IncrementLint

  • 问题三:如何做增量

  原则上增量是需要在全量的基础上的,但是基本上我们的代码都是有git或svn工具管理的,因此可以直接使用使用该工具做差量。另外,由于git或svn都只是管理java代码或资源文件,导致自定义的lint无法检查class文件。如果自定义的规则里面有关于class文件检查的部分,这种增量方式是无法使用的。

LintTask流程

  AS中有很多的lint任务包括lintDebug、lintRelease等,每在productFlavors添加一个Flavor就会多出对应的lint{FlavorName}Debug和lint{FlavorName}Release,这些都是由LintBaseTask衍生出的。我们先看LintBaseTask源码:

public abstract class LintBaseTask extends DefaultTask {
 ......
 //task执行lint入口
 protected void runLint(LintBaseTaskDescriptor descriptor) {
   FileCollection lintClassPath = getLintClassPath();
   if (lintClassPath != null) {
     new ReflectiveLintRunner().runLint(getProject().getGradle(),descriptor,lintClassPath.getFiles());
   }
 }

 //获取BuildTools LintGradleClient所需参数之一
 private BuildToolInfo getBuildTools() {
   TargetInfo targetInfo = androidBuilder.getTargetInfo();
   Preconditions.checkState(
   targetInfo != null, "androidBuilder.targetInfo required for task '%s'.", getName());
   return targetInfo.getBuildTools();
 }

 //LintBaseTask描述者,在runLint中使用,后续实例化LintGradleClient的各个参数都是由这里获取
 protected abstract class LintBaseTaskDescriptor extends com.android.tools.lint.gradle.api.LintExecutionRequest {
 ......
 }

 //获取VariantInputs LintGradleClient所需参数之一,直接new VariantInputs()就行了
 public static class VariantInputs implements com.android.tools.lint.gradle.api.VariantInputs {
 ......
 }

 //该类还不知道干嘛用的,之前提到的lint{FlavorName}Debug任务应该就是和这个有关,不知哪位大神知道,麻烦告知一下
 public abstract static class BaseCreationAction<T extends LintBaseTask> extends TaskCreationAction<T> {

   @Override
   public void configure(@NonNull T lintTask) {
     lintTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
     lintTask.lintOptions = globalScope.getExtension().getLintOptions();
     File sdkFolder = globalScope.getSdkHandler().getSdkFolder();
     if (sdkFolder != null) {
       lintTask.sdkHome = sdkFolder;
     }
     lintTask.toolingRegistry = globalScope.getToolingRegistry();
     lintTask.reportsDir = globalScope.getReportsDir();
     lintTask.androidBuilder = globalScope.getAndroidBuilder();
     lintTask.lintClassPath = globalScope.getProject().getConfigurations().getByName(LINT_CLASS_PATH);
   }
 }
}

  runLint就是task执行的操作,它其实就初始化了ReflectiveLintRunner,然后把LintBaseTaskDescriptor传入。之后初始化LintGradleClient需要的参数大部分都是由LintBaseTaskDescriptor这个对象获取的。

  ReflectiveLintRunner这个类在lint-gradle-api包中,这个类的runLint方法就利用反射实例化LintGradleExecution对象,并调用analyze方法。

fun runLint(gradle: Gradle, request: LintExecutionRequest, lintClassPath: Set<File>) {
 .....
 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)
 ......
}

  LintGradleExecution在lint-gradle包中,LintGradleClient也在这里。analyze方法获取了ToolingRegistry和AndroidProject,并根据变种(variant)走不同的LintGradleClient初始化参数获取流程。

public class LintGradleExecution {
 .......
 public void analyze() throws IOException {
 //最后获取的是LintBaseTask的toolingRegistry,该值在BaseCreationAction中configure中设置到LintBaseTask中
 ToolingModelBuilderRegistry toolingRegistry = this.descriptor.getToolingRegistry();  
 if (toolingRegistry != null) {
   AndroidProject modelProject = createAndroidProject(this.descriptor.getProject(), toolingRegistry);
   String variantName = this.descriptor.getVariantName();
   if (variantName != null) {
     Iterator var4 = modelProject.getVariants().iterator();
     while(var4.hasNext()) {
       Variant variant = (Variant)var4.next();
       if (variant.getName().equals(variantName)) {
         this.lintSingleVariant(variant);
         return;
       }
     }
   } else {
     this.lintAllVariants(modelProject);
  }
 } else {
   this.lintNonAndroid();
 }
}

 //创建AndroidProject LintGradleClient所需参数之一
protected static AndroidProject createAndroidProject(Project gradleProject,ToolingModelBuilderRegistry toolingRegistry) {
 String modelName = AndroidProject.class.getName();
 ToolingModelBuilder modelBuilder = toolingRegistry.getBuilder(modelName);
 assert modelBuilder.canBuild(modelName) : modelName;
 ExtraPropertiesExtension ext = gradleProject.getExtensions().getExtraProperties();
 synchronized(ext) {
 ext.set("android.injected.build.model.only.versioned", Integer.toString(3));
 ext.set("android.injected.build.model.disable.src.download", true);
 AndroidProject var6;
 try {
   var6 = (AndroidProject)modelBuilder.buildAll(modelName, gradleProject);
 } finally {
   ext.set("android.injected.build.model.only.versioned", (Object)null);
   ext.set("android.injected.build.model.disable.src.download", (Object)null);
 }
 return var6;
 }
}
.....
private Pair<List<Warning>, LintBaseline> runLint(Variant variant, VariantInputs variantInputs, boolean report, boolean isAndroid, boolean allowFix) {
 IssueRegistry registry = createIssueRegistry(isAndroid);
 LintCliFlags flags = new LintCliFlags();
 LintGradleClient client = new LintGradleClient(this.descriptor.getGradlePluginVersion(), registry, flags, this.descriptor.getProject(), this.descriptor.getSdkHome(), variant, variantInputs, this.descriptor.getBuildTools(), isAndroid, variant != null ? variant.getName() : null);
 ......
 LintOptions lintOptions = this.descriptor.getLintOptions();
 boolean fix = false;
 if (lintOptions != null) {
 syncOptions(lintOptions, client, flags, variant, this.descriptor.getProject(), this.descriptor.getReportsDir(), report, fatalOnly, allowFix);
 } else {
 .....
 }
 ......
 warnings = client.run(registry);
 .....
 }
}

  lintSingleVariant、lintAllVariants、lintNonAndroid最后都是进入到runLint,在这个函数中进行LintGradleClient对象初始化并获取在build.gradle中配置lintOptions,然后执行LintGradleClient对象的run方法,到这就进入到了lint检查流程,后续gradle没有关系了,具体lint检查流程参考Android Lint工作原理剖析.

修复问题

1、插件在kotlin和java混合项目上报错

  由于插件中的kotlin版本和项目的相互冲突,导致插件会在执行lint检查的时候报错。参考gradle源码中写法,使用ClassLoader将lint的执行环境隔离开,这样就能解决kotlin版本冲突。具体修改参照LintRunner.java

2、自定义的lint规则无法使用

  https://www.jianshu.com/p/01f813c09589

参考

1、Android Lint增量扫描实战纪要
2、Android Lint工作原理剖析
3、eagleeye-gradle-codescanning

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容