💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 自定义Lint 在实际开发中,我们会根据需要,制定一些编码规范、封装公共库、编码最佳实践。在持续迭代的过程中会面对以下问题: * 团队有了新加入的开发者,我们需要一种低成本方式去帮助他,从原来的编码习惯中迅速调整过来,了解、使用我们的命名惯例、公共库、最佳实践。 * 重构老代码时,我们也需要一种低成本的方式将老代码中与我们现有规范有出入的地方,识别出来进行重构,尽可能快的统一规范。 基于以上考虑,我们开始静态代码检测方面的调研工作。Google在这方面提供了Lint工具,跟着谷爹走,无疑是一条捷径。 > 相关代码托管在[Git仓库](https://git.youxinpai.com/AndroidSdk/LintX) ## 一、Lint SDK ### 1.1、Lint相关api Android SDK中涉及Lint的主要有下面几个包,均包含在Android Gradle插件com.android.tools.build:gradle中。 * **com.android.tools.lint:lint-api**<br> 这个包提供了Lint的API,包括Context、Project、Detector、Issue、IssueRegistry等。 * **com.android.tools.lint:lint-checks**<br> 这个包实现了Android原生Lint规则。在25.3.3版本中,BuiltinIssueRegistry中共包含两百多条Lint规则。 * **com.android.tools.lint:lint**<br> 这个包用于运行Lint检查,提供: * **com.android.tools.lint.XxxReporter**<br> 检查结果报告,包括纯文本、XML、HTML格式等 * **com.android.tools.lint.LintCliClient**<br> 用于在命令行中执行Lint * **com.android.tools.lint.Main**<br> 这个类是命令行版本Lint的Java入口(Command line driver),主要是解析参数、输出结果 * **com.android.tools.build:gradle-core**<br> 这个包提供Gradle插件核心功能,其中与Lint相关的主要有: * **com.android.build.gradle.internal.LintGradleProject**<br> 继承自lint-api中的Project类。Gradle执行Lint检查时使用的Project对象,可获取Manifest、依赖等信息。其中又包含了AppGradleProject和LibraryProject两个内部类。 * **com.android.build.gradle.internal.LintGradleClient**<br> 用于在Gradle中执行Lint,继承自LintCliClient * **com.android.build.gradle.tasks.Lint**<br> Gradle中Lint任务的实现 > 如果对lint工具源码实现有兴趣的同学,可以查看这三篇文章:[第一篇](https://hujiaweibujidao.github.io/blog/2016/12/01/builtin-lint-detectors-1/)、[第二篇](https://hujiaweibujidao.github.io/blog/2016/11/18/lint-tool-analysis-2/)、[第三篇](https://hujiaweibujidao.github.io/blog/2016/11/19/lint-tool-analysis-3/)。 ### 1.2、Lint命令行实现 Lint可执行文件位于`<android-home>/tools/lint`,是一个Shell脚本,配置相关参数并执行Java调用`com.android.tools.lint.Main`进行检查。 ### 1.3、IDEA中的实现 在Android Studio或装有Android插件的IDEA环境下,Inspections中的Lint检查是通过Android插件实现的,代码实现主要在`org.jetbrains.android.inspections.lint`包中。 [IDEA Android插件中Lint部分的实现](https://github.com/JetBrains/android/blob/master/android/src/org/jetbrains/android/inspections/lint) ### 1、4、Lint兼容性 自从ADT 16第一次引入Android Lint(以下简称:Lint)以来,Lint便成为Android平台上最重要的静态代码扫描工具。与早期基于XPath的静态扫描工具不同,Lint基于AST(Abstract Syntax Tree)进行分析,可以用来定制很复杂的扫描规则。 Lint从第一个版本就选择了**lombok-ast**作为自己的AST Parser,并且用了很久。但是Java语言本身在不断更新,Android也在不断迭代出新,lombok-ast慢慢跟不上发展,所以Lint在25.2.0版增加了IntelliJ的**PSI**(Program Structure Interface)作为新的AST Parser。但是PSI于IntelliJ、于Lint也只是个过渡性方案,事实上IntelliJ早已开始了新一代AST Parser,**UAST**(Unified AST)的开发,而Lint在25.4.0版后已经将PSI更新为UAST。 在Lint兼容性方面(这里只说代码文件),25.4.0之前版本中,Detector在提供`JavaPsiScanner`的同时保留了旧版的`JavaScanner`接口。但在25.4.0后的版本中完全移除了JavaScanner、JavaPsiScanner,推荐使用新的UastScanner。 UAST是JetBrains在IDEA新版本中用于替换PSI的API。UAST更加语言无关,除了支持Java,还可以支持Kotlin。 因此,在自定义Lint规则时,需要考虑Scanner的接口兼容性 * JavaPsiScanner<br> * `com.android.tools.build:gradle`:2.2+到2.3.3版本 * `com.android.tools.lint:lint-api`:25.3.0及以下版本 * uastscanner<br> * `com.android.tools.build:gradle`3.0.0及以上 * `com.android.tools.lint:lint-api`25.4.0及以上版本 ## 二、自定义Lint 在开始自定义lint前,先对相关API进行简单介绍: 自定义Lint开发需要调用Lint提供的API,最主要的几个API如下: * **Issue**<br> 表示一个Lint规则。例如调用Toast.makeText()方法后,没有调用Toast.show()方法将其显示。 * **Detector**<br> 用于检测并报告代码中的Issue。每个Issue包含一个Detector。 * **Scope**<br> 声明Detector要扫描的代码范围,例如Java源文件、XML资源文件、Gradle文件等。每个Issue可包含多个Scope。 * **Scanner**<br> 用于扫描并发现代码中的Issue。每个Detector可以实现一到多个Scanner。自定义Lint开发过程中最主要的工作就是实现Scanner。 * **IssueRegistry**<br> 用于注册要检查的Issue列表。自定义Lint需要生成一个jar文件,其Manifest指向IssueRegistry类。 以上主要几个API出Scanner,在实现自定义Lint Rules都是相对固定的写法,相对复杂的则是Scanner重载方法的实现上。 下面通过一个简单的自定义示例来进行说明: ```java /** * Log规范Detector * <p> * 使用 内部日志库Logx 替代 常规Log\println * <p> * 实际效果: * ban:android.util.Log、System.out.println * pick:com.xin.support.Logx * <p> * 检测代码中存在 使用android.util.Log、System.out.println 来输出日志时 ,提醒使用内部库Logx */ public class LogDetector extends Detector implements Detector.JavaPsiScanner { private static final Class<? extends Detector> DETECTOR_CLASS = LogDetector.class; private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE; private static final String ISSUE_ID = "LintX__LogUseError"; private static final String ISSUE_DESCRIPTION = "LintX__应该使用公共组件库 LogX来输出日志信息{com.xin.support.LogX}"; private static final String ISSUE_EXPLANATION = "LintX__为了能够更好的控制Log打印的开关,你不能直接使用{android.util.Log}直接打印日志,应该使用公共组件库LogX{com.xin.support.LogX}"; private static final Category ISSUE_CATEGORY = LintXCategory.BASELIBS; private static final int ISSUE_PRIORITY = 9; private static final Severity ISSUE_SEVERITY = Severity.WARNING; private static final String CHECK_PACKAGE = "android.util.Log"; //固定区域 private static final Implementation IMPLEMENTATION = new Implementation( DETECTOR_CLASS, DETECTOR_SCOPE ); public static final Issue ISSUE = Issue.create( ISSUE_ID, ISSUE_DESCRIPTION, ISSUE_EXPLANATION, ISSUE_CATEGORY, ISSUE_PRIORITY, ISSUE_SEVERITY, IMPLEMENTATION ); public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e"); } public void visitMethod(JavaContext context, JavaElementVisitor visitor, PsiMethodCallExpression node, PsiMethod method) { JavaEvaluator evaluator = context.getEvaluator(); if (evaluator.isMemberInClass(method, CHECK_PACKAGE)) { context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION); } } } ``` ### 2.1、创建工程 ```groovy apply plugin: 'java' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.tools.lint:lint-api:24.5.0' compile 'com.android.tools.lint:lint-checks:24.5.0' } ``` ### 2.2、创建ISSUE Lint提供了一个`create()`的静态工程方法来创建Issue。 ```java public static Issue create( @NonNull String id, @NonNull String briefDescription, @NonNull String explanation, @NonNull Category category, int priority, @NonNull Severity severity, @NonNull Implementation implementation) ``` 需要以下几个参数: * **ID**<br> 唯一值,应该能简短描述当前问题。利用Java注解或者XML属性进行屏蔽时,使用的就是这个id * **briefDescription**<br> 简短的总结,通常5-6个字符,描述问题而不是修复措施 * **explanation**<br> 完整的问题解释和修复建议。 * **category**<br> 问题类别。除了lint官方自带的几个类别,还可以进行自定义。 * **priority**<br> 优先级。1-10的数字,10为最重要/最严重 * **severity**<br> 严重级别:Fatal, Error, Warning, Informational, Ignore。 * **Implementation**<br> 为Issue和Detector提供映射关系,Detector就是当前Detector。声明扫描检测的范围Scope,Scope用来描述Detector需要分析时需要考虑的文件集,包括:Resource文件或目录、Java文件、Class文件。 以上几项都很简单,除了自定义category和Scope外,没有太多需要额外说明的地方。 ##### Category 系统现在已有的类别如下: * Lint * Correctness (incl. Messages) * Security * Performance * Usability (incl. Icons, Typography) * Accessibility * Internationalization * Bi-directional text 在实际开发中,为了让我们检测出的问题跟其他问题区分开来,让开发者更有针对性的筛选处理问题,我们都会进行自定义Category ![](http://pactji2yy.bkt.clouddn.com/15359636219725.jpg) lint同样提供了一个静态工厂方法用来自定义Category: ```java public class LintXCategory { public static final Category STANDARD = Category.create("编码规范", 107); public static final Category BASELIBS = Category.create("公共库推广", 105); } ``` ##### Scope Scope主要用来划定Lint 目标检查对象的,目前提供以下枚举值: * **ALL_CLASS_FILES**<br> 将所有Java类文件一起 检测 * **ALL_JAVA_FILES**<br> 将所有Java源文件一起检测 * **ALL_RESOURCE_FILES**<br> 检测所有资源文件 * **BINARY_RESOURCE_FILE**<br> 只检测单个二进制(通常是位图)资源文件 * **CLASS_FILE**<br> 只检测一个Java类文件 * **GRADLE_FILE**<br> 检测Gradle构建文件 * **JAVA_FILE**<br> 只检测单个Java源文件 * **JAVA_LIBRARIES**<br> 检测该项目的库中的classes文件 * **MANIFEST**<br> 检测清单文件 * **PROGUARD_FILE**<br> 检测Proguard配置文件 * **PROPERTY_FILE**<br> 检测Java属性文件 * **RESOURCE_FILE**<br> 只检测单个XML资源文件 * **RESOURCE_FOLDER**<br> 检测资源文件夹(还包括资产文件夹 * **TEST_SOURCES**<br> 检测test源代码 * **OTHER**<br> 其他文件 根据自定义的需求,选择以上枚举值即可。 ### 2.3、重载Scanner相关方法 自定义很重要的一步是继承Detector,并实现XXXScanner的相关方法。 自定义Detector可以实现一个或多个Scanner接口,选择实现哪种接口取决于你想要的扫描范围。目前提供了以下接口: * Detector.XmlScanner * Detector.JavaScanner * Detector.ClassScanner * Detector.BinaryResourceScanner * Detector.ResourceFolderScanner * Detector.GradleScanner * Detector.OtherFileScanner 一般来说,Scanner的方法都是成对出现: * 一个决定什么样的类型能被检测到,返回值通常都是一个集合 * 另一个方法接收上一个方法的返回,并进行具体的逻辑判断就行。如果符合Issue预期条件,调用Context的report方法报告问题即可。 用于报告问题的report方法相对简单,这里先说明下。 ```java context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION); ``` 第一个参数就是上面提到Issue; 第二个参数是单点的节点; 第三个参数Location会返回当前的位置信息,便于在报告中显示定位; 最后的字符串用来为警告添加解释,通常使用Issue中的简述即可; ![](http://pactji2yy.bkt.clouddn.com/15359722887556.jpg) 这里还需要说明report会自动处理被suppress(`suppressLint`)/ignore(`tools:ignore`)的警告。所以发现问题直接调用`report`就可以,不用担心其他问题。 难点来了,Scanner以及Psi AST相关文档信息和具体实现都不多,这里先简单提一下主要方法,后续会有单独文章进行说明。 | Psi | Psi成对方法 | 释义 | | :-- | :-- | :-- | | getApplicablePsiTypes | createPsiVisitor(JavaContext context) | 此方法返回需要检查的AST节点的类型,类型匹配的UElement将会被createUastHandler(createJavaVisitor)创建的UElementHandler(Visitor)检查 | | getApplicableMethodNames | visitMethod(JavaContext,JavaElementVisitor, PsiMethodCallExpression, PsiMethod) | 返回你所需要检查的方法名称列表,或者返回null,相匹配的方法将通过visitMethod方法被检查 | | getApplicableConstructorTypes | visitConstructor(JavaContext, JavaElementVisitor,PsiNewExpression,PsiMethod) | 返回需要检查的构造函数类型列表,类型匹配的方法将通过visitConstructor被检查 | | getApplicableReferenceNames | visitReference(JavaContext,JavaElementVisitor,PsiJavaCodeReferenceElement,PsiElement) | 返回需要检查的引用路径名,匹配的引用将通过visitReference被检查 | | applicableSuperClasses | checkClass(JavaContext, PsiClass) | 返回需要检查的父类名列表,此处需要类的全路径名,visitClass会检查applicableSuperClasses返回的类 | 顺便贴一下Uast的相关重载方法,便于后续的升级重构,命名很相似。 | Uast | Uast成对方法 | 释义 | | :-- | :-- | :-- | | getApplicableUastTypes() | createUastHandler(JavaContext) | 同Psi | | getApplicableMethodNames() |visitMethod(JavaContext, UCallExpression, PsiMethod) | 同Psi| | getApplicableConstructorTypes() | visitConstructor(JavaContext, UCallExpression, PsiMethod) | 同Psi | | getApplicableReferenceNames() | visitReference(JavaContext, UReferenceExpression, PsiElement) | 同Psi | | applicableSuperClasses() | visitClass(JavaContext, UClass) | 同Psi | 更多关于Psi、Uast的用法可以[看这里](https://github.com/ouyangpeng/android-lint-checks-studio3.0) ### 2.4、注册ISSUE 将Issue注册只需要继承IssueRegistry,实现getIssues方法即可。 示例如下: ```java /** * IssueRegistry 自定义实现类 * 在getIssues()返回 需要自定义检测的类 */ public class LintXIssueRegistry extends IssueRegistry { private final static String VERSION_LINTX = "0.0.12"; static { //用我们的修改版 替换一些 自带的lint检查 List<Issue> systemIssues = new BuiltinIssueRegistry().getIssues(); List<Issue> systemIssuesNew = new ArrayList<>(systemIssues.size()); systemIssuesNew.addAll(systemIssues); //替换ResourcePrefixDetector (仅改动Category,便于分类) systemIssuesNew.remove(ResourcePrefixDetector.ISSUE); systemIssuesNew.add(ResourcePrefixProDetector.ISSUE); try { Field field = BuiltinIssueRegistry.class.getDeclaredField("sIssues"); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, systemIssuesNew); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override public synchronized List<Issue> getIssues() { System.out.println("==== lintx start-" + VERSION_LINTX + " ===="); return Arrays.asList( LogDetector.ISSUE , ToastUtilsDetector.ISSUE , ResourcePrefixProDetector.ISSUE ); } } ``` 并且设置将jar包的manifest制定为全类名即可。 这里附上Rules jar包的全部build.gradle代码 ```groovy apply plugin: 'java' def lint_version = "25.3.0" dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.tools.lint:lint-api:' + lint_version compile 'com.android.tools.lint:lint-checks:' + lint_version testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.0.0' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'com.android.tools.lint:lint:' + lint_version testCompile 'com.android.tools.lint:lint-tests:' + lint_version testCompile 'com.android.tools:testutils:' + lint_version } jar { manifest { attributes("Lint-Registry": "com.xin.support.lintx.rules.LintXIssueRegistry") } } defaultTasks 'assemble' /* * rules for providing "lint.jar" */ configurations { lintJarOutput } dependencies { lintJarOutput files(jar) } ``` 好啦,通过以上的工作,现在我们就能生成一个包含简单自定义规则的lint.jar ## 三、AAR包装 上一节中自定义lint规则会生成一个**lint.jar**,Google提供的使用方式**将jar拷贝到~/.android/lint中**。 以上这种方式,推广实现的时候比较麻烦,需要开发者手动进行配置。还有一个缺点: 针对所有工程,会影响同一台机器其他工程的Lint检查。即便触发工程时拷贝过去,执行完删除,但其他进程或线程使用./gradlew lint仍可能会受到影响。 翻阅美团等团队的lint方案时,发现都参考借鉴了**LinkedIn**提供的思路:**将lint.jar放入一个aar中,需要检测的工程依赖该aar。 想要看LinkedIn的同学可以看博文:[Writing Custom Lint Checks with Gradle](https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle) 下面我们简单说说 如何将lint.jar包装成aar。 整个module仅仅将上面生成的jar文件 包装成一个aar即可,整个module只需要一个build.gradle。 ```groovy apply plugin: 'com.android.library' android { compileSdkVersion 27 buildToolsVersion "27.0.2" defaultConfig { minSdkVersion 9 targetSdkVersion 25 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } /* * 定义方法lintJarImport,引入上一步生成的LintXRules.jar * * rules for including "lint.jar" in aar */ configurations { lintJarImport } // 链接到lintJar中的lintJarOutput方法,调用jar方法,并获得jar包 dependencies { //其引用了模块 “:LintXRules”的 Gradle configuration “lintJarOutput”。 lintJarImport project(path: ":LintXRules", configuration: "lintJarOutput") } //创建一个copyLintJar的task:将得到的JAR包复制到目录build/intermediates/lint/下,并且重命名为 lint.jar task copyLintJar(type: Copy) { from (configurations.lintJarImport) { rename { String fileName -> 'lint.jar' } } into 'build/intermediates/lint/' } // 当项目build到compileLint这一步时执行copyLintJar方法 project.afterEvaluate { def compileLintTask = project.tasks.find { it.name == 'compileLint' } //对内置的Gradle task “compileLint”做了修改,让其依赖于我们定义的一个task “copyLintJar”。 compileLintTask.dependsOn(copyLintJar) } ``` 这里说明下该mudule进行的工作: 1. 链接到lintJar中的lintJarOutput方法,调用jar方法,并获得jar包 2. 将得到的JAR包复制到目录build/intermediates/lint/下,并且重命名为 lint.jar 3. 当项目build到compileLint这一步时执行copyLintJar方法,这样的话就可以调用到我们自定义的Lint规则 4. 生成AAR方便项目调用 我们把该aar上传到maven私服上,公司的所有团队就能进行依赖,配置完lint.xml、lintOptions后,运行指令`./gradlew lint`,就能够获取lint-report报告了。 ## 四、Lint plugin 相比最开始的拷贝lint.jar,依赖aar相对简单一些。但依然会存在一些问题,不便于团队规范的统一: * 配置繁琐。每个项目都需要依赖aar,自行配置lint.xml、lintOptions,接入成本大。 * 规范不统一。每个项目都是自行定义的lintOptions,随着版本迭代,各个项目之间的lint规则,会越来越不一致,难以达到已开始“统一规范”的初心。 基于以上考虑,并参考其他开源项目的实践情况,这里会创建一个plugin,开发团队只需要依赖该插件即可。 这里简述以下处理逻辑,相关实现可以查阅具体的代码: * 开始lint操作时,会将plugin内置的lint.xml拷贝到指定目录,执行完lint操作后会删除该文件,避免影响其他操作; * 使用plugin 自带的lintOption替换默认的配置; * 自动下载依赖maven上最新的aar,保持实时更新; 通过这个插件: * 接入简单。一行代码就可以完成接入; * 规范统一。所有团队的配置和规则都会是最新的; 这里是主体功能实现,完整代码可以在gitlab上查看: ```groovy class LintXPlugin implements Plugin<Project> { @Override void apply(Project project) { applyTask(project, getAndroidVariants(project)) } private static final String sPluginMisConfiguredErrorMessage = "Plugin requires the 'android' or 'android-library' plugin to be configured."; /** * 获取project 项目 中 android项目 或者library项目 的 variant 列表 * @param project 要编译的项目 * @return variants列表 */ private static DomainObjectCollection<BaseVariant> getAndroidVariants(Project project) { if (project.getPlugins().hasPlugin(AppPlugin)) { return project.getPlugins().getPlugin(AppPlugin).extension.applicationVariants } if (project.getPlugins().hasPlugin(LibraryPlugin)) { return project.getPlugins().getPlugin(LibraryPlugin).extension.libraryVariants } throw new ProjectConfigurationException(sPluginMisConfiguredErrorMessage, null) } /** * 插件的实际应用:统一管理lint.xml和lintOptions,自动添加aar。 * @param project 项目 * @param variants 项目的variants */ private void applyTask(Project project, DomainObjectCollection<BaseVariant> variants) { //========================== 统一 自动添加AAR 开始=============================================// //配置project的dependencies配置,默认都自动加上 自定义lint检测的AAR包 project.dependencies { if(project.getPlugins().hasPlugin('com.android.application')){ compile('com.xin.support:lintx:+') { // compile('com.xin.support:lintx:latest.release') { force = true; } } else { provided('com.xin.support:lintx:+') { // provided('com.xin.support:lintx:latest.release') { force = true; } } } //去除gradle缓存的配置 project.configurations.all { resolutionStrategy.cacheChangingModulesFor 0, 'seconds' resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds' } //========================== 统一 自动添加AAR 结束=============================================// def archonTaskExists = false variants.all { variant -> //获取Lint Task def variantName = variant.name.capitalize() Lint lintTask = project.tasks.getByName("lint" + variantName) as Lint //Lint 会把project下的lint.xml和lintConfig指定的lint.xml进行合并,为了确保只执行插件中的规则,采取此策略 File lintFile = project.file("lint.xml") File lintOldFile = null //========================== 统一 lintOptions 开始=============================================// /* lintOptions { lintConfig file("lint.xml") warningsAsErrors true abortOnError true htmlReport true htmlOutput file("lint-report/lint-report.html") xmlReport false } */ def newOptions = new LintOptions() newOptions.lintConfig = lintFile newOptions.warningsAsErrors = true newOptions.abortOnError = false newOptions.htmlReport = true //不放在build下,防止被clean掉 newOptions.htmlOutput = project.file("${project.projectDir}/lint-report/lint-report.html") newOptions.xmlReport = false newOptions.xmlOutput = project.file("${project.projectDir}/lint-report/lint-report.xml") lintTask.lintOptions = newOptions //========================== 统一 lintOptions 结束=============================================// lintTask.doFirst { //如果 lint.xml 存在,则改名为 lintOld.xml if (lintFile.exists()) { lintOldFile = project.file("lintOld.xml") lintFile.renameTo(lintOldFile) } //进行 将plugin内置的lint.xml文件和项目下面的lint.xml进行复制合并操作 def isLintXmlReady = copyLintXml(project, lintFile) if (!isLintXmlReady) { if (lintOldFile != null) { lintOldFile.renameTo(lintFile) } throw new GradleException("lint.xml不存在") } } //lint任务执行后,删除lint.xml project.gradle.taskGraph.afterTask { task, TaskState state -> if (task == lintTask) { lintFile.delete() if (lintOldFile != null) { lintOldFile.renameTo(lintFile) } } } //========================== 统一 lint.xml 结束=============================================// //========================== 在终端 执行命令 gradlew lintx 的配置 开始=============================================// // 在终端 执行命令 gradlew lintForXTC 的时候,则会应用 lintTask if (!archonTaskExists) { archonTaskExists = true //创建一个task 名为 lintx project.task("lintx").dependsOn lintTask } //========================== 在终端 执行命令 gradlew lintx 的配置 结束=============================================// } } } ``` ## 参考文献 * [Android Lint:自定义Lint调试与开发](http://www.paincker.com/android-lint-2-implements) * [美团:Android自定义Lint实践](https://tech.meituan.com/android_custom_lint.html) * [Android自定义Lint实践](https://blog.csdn.net/ouyang_peng/article/details/80374867) * [LinkedIn:Writing Custom Lint Checks with Gradle](https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle) * [Android工具:被你忽视的Lint](https://blog.csdn.net/p106786860/article/details/54187138) * [lint-dev](https://groups.google.com/forum/#!forum/lint-dev) * [Android Lint:基本使用與配置](https://hk.saowen.com/a/ccfc72ee25ec59ffd6d4c976aa1f10aef3b46f53403b1cf50f20c8b33d90e445) * [Android Lint增量扫描实战纪要](https://blog.csdn.net/H176Nhx7/article/details/78870470) * [google Lint源代码地址](https://android.googlesource.com/platform/tools/base/+/studio-3.0/lint/)