Lint 是 Android Studio 内置的一个静态代码扫描工具,它能够检查 Android 项目源文件是否包含潜在错误,以及在正确性、安全性、性能、易用性、便利性和国际化方面是否须要优化改进。AS 已经内置了大量的 Lint 检查规则,可是当咱们须要定制化规则时,就须要考虑自定义 Lint 了。html
首先,先要仔细想一想,何时须要用到自定义 Lint 呢?java
这就要发挥你的想象力了,我最初是想推 webp,用以替换掉 png,这样就须要开发者每次自觉的将 png 转化为 webp,可是又不可能结对编程,监督他进行转化,因此自定义 Lint 就派上用场了。android
可是,目前网上的绝大多数博客都还停留在 AS 2.x 的 Lint API 上,关于 AS 3.x Lint API 的几乎是空白。从 AS 2.x 到 AS 3.x,Lint API 变化还挺大的,特别是对 Java 源文件的检测,从 JavaScanner 过渡到 JavaPsiScanner 再到 UastScanner,自定义 Lint 的注册以及引用方式也都发生了变化。本文也是从 Google 的一个 Sample 再参考系统内置的 Lint 实现,一点点摸索出来的。git
如下是项目中的自定义 Lint 实现效果:github
以上 Detector 都有什么用呢?web
ToastDetector:Android 自带的 Lint 规则,用于提示 Toast 忘记调用 show 方法,在 BuiltinIssueRegistry 类能够查看内置的全部的 Detector,这也是我实现自定义 Lint 的主要参考依据。express
SampleCodeDetector:Google Sample 中的 Demo,用于检测文本表达式中是否包含特定的字符串。编程
PngDetector:用于检测全部 layout 或 java 文件中引用的 png 资源,提示使用 webp。api
LogDetector:用于检测使用 Log 类的 i、d、e 等日志输出的方法,提示使用统一的日志工具类。严重程度为 Error,因此默认状况下,会中断编译流程。若是严重程度为 Warning,仅仅是报黄警告。安全
ThreadDetector:用于检测直接经过 new Thread 建立线程,提示应该使用统一线程池。
先来看一个简单的自定义 Lint 是怎么样一步一步写出来的,该例子实际上来自 github.com/googlesampl… ,该 Rep 就一个 SampleCodeDetector,也就是上文中所说的。
自定义 Lint 一共能够分为四步:
在 build.gradle 文件里添加依赖:
compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"复制代码
public class SampleDetector extends Detector implements Detector.UastScanner {
//第一步:定义 ISSUE
public static final Issue ISSUE = Issue.create(
"ShortUniqueId", //惟一 ID
"Lint Mentions", //简单描述
"Blah blah blah.", //详细描述
Category.CORRECTNESS, //问题种类(正确性、安全性等)
6, //权重
Severity.WARNING, //问题严重程度(忽略、警告、错误)
new Implementation( //实现,包括处理实例和做用域
SampleCodeDetector.class,
Scope.JAVA_FILE_SCOPE));
//第二步:定义检测类型以及处理逻辑
//检测类型包括文本表达式、调用相关表达式等
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(ULiteralExpression.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitLiteralExpression(@NotNull ULiteralExpression expression) {
String string = UastLiteralUtils.getValueIfStringLiteral(expression);
if (string == null) {
return;
}
if (string.contains("Omooo") && string.matches(".*\\bOmooo\\b.*")) {
//第三步:符合条件,上报 ISSUE
context.report(ISSUE, expression, context.getLocation(expression),
"This code mentions `Omooo`");
}
}
};
}
}复制代码
Lint API 中内置了不少 Scanner:
Scanner 类型 | Desc |
---|---|
UastScanner | 扫描 Java、Kotlin 源文件 |
XmlScanner | 扫描 XML 文件 |
ResourceFolderScanner | 扫描资源文件夹 |
ClassScanner | 扫描 Class 文件 |
BinaryResourceScanner | 扫描二进制资源文件 |
更多请参考:static.javadoc.io/com.android…
注意:
这里须要注意的一点是,若是对应的 ISSUE 严重程度为错误(Severity.ERROR),那么在默认状况下,会中断编译流程,固然,你也能够配置 LintOptions 来抑制 Lint 错误。
public class CustomIssueRegistry extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
return Arrays.asList(
SampleCodeDetector.ISSUE);
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}复制代码
这里能够注册多个 Detector,目前最新版本的 Lint 内置了 360 种 Detector,都在 BuiltinIssueRegistry 类中,能够做为咱们编写自定义 Lint 的最佳参考案例。
首先须要在 lint_library 中的 build.gradle 文件中添加,完整代码为:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
jar {
manifest {
attributes("Lint-Registry-v2": "top.omooo.lint_library.CustomIssueRegistry")
}
}复制代码
而后就能够在 app module 中的 build.gradle 中引入并使用了:
dependencies {
//...
lintChecks project(":lint_library")
}复制代码
以上,一个简单的自定义 Lint 就写完了,可是有点意犹未尽的感受。这时候就须要你发挥想象力,想一想本身须要什么。你能够参考我给的源码,或者参考 Android 内置的 Lint 源码,看看它们能作什么。
这一小节很重要,可是我并不会给你讲如何去实现某某功能,本身看源码学习,由于真的不难哇。
若是你很懒,很烦每次都敲一遍 ./gradlew lint 去查看 Lint 输出,那么能够把执行 Lint 任务挂载在每次安装 Debug 包以前,即:
/**
* 在执行 assembleDebug Task 以前挂载 lintDebug
*/
project.afterEvaluate {
def assembleDebugTask = project.tasks.find { it.name == 'assembleDebug' }
def lintTask = project.tasks.find { it.name == 'lintDebug' }
assembleDebugTask.dependsOn(lintTask)
}复制代码
(逃~