什么是Lint?
Lint 的代码扫描工具,可帮助你发现并更正代码结构质量的问题,而无需您实际执行应用,也不必编写测试用例。
Lint都能检测什么
- Java
- Kotlin
- XML文件
- 图标
- ProGuard 配置文件
Lint 配置为抑制警告
默认情况下,运行 Lint 扫描时,Lint 工具会检查它支持的所有问题。你也可以限制 Lint 要检查的问题,并为这些问题分配严重级别。例如你可以禁止对与项目无关的特定问题进行 Lint 检查,也可以将 Lint 配置为以较低的严重级别报告非关键问题
1.lint.xml
可以通过lint.xml文件中的<issue>
标签标记中设置严重级别属性来更改某个问题的严重级别或对该问题停用 Lint 检查
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- 在项目中禁止检测 id为 IconMissingDensityFolder的问题-->
<issue id="IconMissingDensityFolder" severity="ignore" />
<!-- 也可以通过 ignore忽略指定id下的指定文件 -->
<issue id="ObsoleteLayoutParam">
<ignore path="res/layout/activation.xml" />
<ignore path="res/layout-xlarge/activation.xml" />
</issue>
<!-- 将硬编码字符串(HardcodedText)的严重程度更改为"error" -->
<issue id="HardcodedText" severity="error" />
</lint>
severity是指定级别的问题,lint系统自带的检测很多的有自己的级别,我们可以通过lint.xml自定义修改问题的严重级别
severity严重级别从上往下
- Fatal
- Error
- Warning
- Informational
- Ignore
2.Gradle 配置 Lint 选项
build.gradle 文件中的 lintOptions {}
android {
// 移除lint检查的error,可以避免由于编译条件太过严格而编译不过的问题
lintOptions {
// 如果为 true,则当lint发现错误时停止 gradle构建
abortOnError false
// 如果为 true,则只报告错误
ignoreWarnings true
// 不检查给定的问题id InvalidPackage: Package not included in Android
disable 'InvalidPackage'
// 不检查给定的问题id 资源类型错误
disable "ResourceType"
// 忽略因MissingTranslation导致Build Failed错误 "app_name"
disable 'MissingTranslation'
// 检查给定的问题 id
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// * 仅 * 检查给定的问题 id
check 'NewApi', 'InlinedApi'
// 配置写入输出结果的位置;它可以是一个文件或 “stdout”(标准输出)
textOutput 'stdout'
// 如果为真,会生成一个XML报告,以给Jenkins之类的使用
xmlReport false
// 用于写入报告的文件(如果不指定,默认为lint-results.xml)
xmlOutput file("lint-report.xml")
// 如果为真,会生成一个HTML报告(包括问题的解释,存在此问题的源码,等等)
htmlReport true
// 写入报告的路径,它是可选的(默认为构建目录下的 lint-results.html )
htmlOutput file("lint-report.html")
// 设置为 true, 将使所有release 构建都以issus的严重性级别为fatal
//(severity=false)的设置来运行lint
// 并且,如果发现了致命(fatal)的问题,将会中止构建
//(由上面提到的 abortOnError 控制)
checkReleaseBuilds true
// 设置给定问题的严重级别(severity)为fatal (这意味着他们将会
// 在release构建的期间检查 (即使 lint 要检查的问题没有包含在代码中)
fatal 'NewApi', 'InlineApi'
// 设置给定问题的严重级别为error
error 'Wakelock', 'TextViewEdits'
// 设置给定问题的严重级别为warning
warning 'ResourceAsColor'
// 设置给定问题的严重级别(severity)为ignore (和不检查这个问题一样)
ignore 'TypographyQuotes'
// 如果为 true,则检查所有的问题,包括默认不检查问题
checkAllWarnings true
// 重置 lint 配置(使用默认的严重性等设置)。
lintConfig file("default-lint.xml")
// 设置为 true,则当有错误时会显示文件的全路径或绝对路径 (默认情况下为true)
absolutePaths true
}
}
Secoo的lint
首先我们采用了 build.gradle 文件中的 lintOptions {}的配置方式
因为项目中有十几个module组成,为每个module单独引用lintOptions{}是一件比较繁琐而且不利于维护的事情,当然如果特殊的module我们也可以单出处理,这也时选择使用gradle配置的原因之一
//抽出公共的lint配置
common_lint_build.gradle
//在common_component_build.gradle中引入 common_lint_build.gradle
apply from:"../common_lint_build.gradle"
注意:项目中有的module没有引入common_component_build.gradle的模块需要单独引入common_lint_build.gradle
自定义Detector
lint支持自定义Detector来对自定义ISSUE检测规则
自定义Detector需要继承自Detector并实现 Detector.UastScanner 接口,25.2.0及之前版本Detector.JavaPsiScanner已被弃用,UastScanner相比于JavaPsiScanner以及更老的JavaScanner,主要提供了对Kotlin支持,API更加简单,特点是成对存在(满足条件 -> visitor)此外可以lint-checks-version.jar中的各类型Detector源码可以学习其用法。
UastScanner包含13个回调方法,下面介绍常用的几个:
1.getApplicableUastTypes
此方法返回需要检查的AST节点的类型,类型匹配的UElement将会被createUastHandler(createJavaVisitor)创建的UElementHandler(Visitor)检查。
2.createUastHandler
创建一个UastHandler来检查需要检查的UElement,对应于getApplicableUastTypes
3.getApplicableMethodNames
返回你所需要检查的方法名称列表,或者返回null,相匹配的方法将通过visitMethod方法被检查
4.visitMethod
检查与getApplicableMethodNames相匹配的方法
5.getApplicableConstructorTypes
返回需要检查的构造函数类型列表,类型匹配的方法将通过visitConstructor被检查
6.visitConstructor
检查与getApplicableConstructorTypes相匹配的构造方法
7.getApplicableReferenceNames
返回需要检查的引用路径名,匹配的引用将通过visitReference被检查
8.visitReference
检查与getApplicableReferenceNames匹配的引用
9.appliesToResourceRefs
返回需要检查的资源引用,匹配的引用将通过visitResourceReference被检查
10.visitResourceReference
检查与appliesToResourceRefs匹配的资源引用
11.applicableSuperClasses
返回需要检查的父类名列表,此处需要类的全路径名
12.visitClass
检查applicableSuperClasses返回的类
ISSUE
ISSUE在每个Detector中定义,lint检查到相关项将ISSUE报告出来,示例:
public static final Issue ISSUE = Issue.create(
"LogUse",
"避免使用Log/System.out.println",
"使用LogUtils,防止在正式包打印log",
Category.SECURITY, 5, Severity.ERROR,
new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)
);
id : 唯一值,应该能简短描述当前问题。利用Java注解或者XML属性进行屏蔽时,使用的就是这个id。
summary : 简短的总结,通常5-6个字符,描述问题而不是修复措施。
explanation : 完整的问题解释和修复建议。
category : 问题类别。详见下文详述部分。
priority : 优先级。1-10的数字,10为最重要/最严重。
severity : 严重级别:Fatal, Error, Warning, Informational, Ignore。
Implementation : 为Issue和Detector提供映射关系,Detector就是当前Detector。声明扫描检测的范围Scope,Scope用来描述Detector需要分析时需要考虑的文件集,包括:Resource文件或目录、Java文件、Class文件。
Category
Category表示lint结果在IDE中的分类,系统已有类别:
- Lint
- Correctness (incl. Messages)
- Security
- Performance
- Usability (incl. Icons, Typography)
- Accessibility
- Internationalization
- Bi-directional text
此外还可以自定义Category
自定义Category
自定义Category,示例:
public class MTCategory {
public static final Category NAMING_CONVENTION = Category.create("命名规范", 101);
}
使用
public static final Issue ISSUE = Issue.create(
"IntentExtraKey",
"intent extra key 命名不规范",
"请在接受此参数中的Activity中定义一个按照EXTRA_<name>格式命名的常量",
MTCategory.NAMING_CONVENTION , 5, Severity.ERROR,
new Implementation(IntentExtraKeyDetector.class, Scope.JAVA_FILE_SCOPE));
IssueRegistry
提供需要被检测的Issue列表(添加自定义Detector)示例:
public class SecooIssueRegistry extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
return Arrays.asList(
LogDetector.ISSUE,
ThreadDetector.ISSUE);
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
@Override
public int getMinApi() { //兼容3.1 不兼容自定义lint一直l不检测 必须加上这个检测了
return 1;
}
}
Secoo的Lint module
为了方便项目扩展,自定义lint专门抽出一个module,以后扩展自定义的lint都可以在该模块下添加
module-lint
具体的配置以及使用的方式在common_lint_build.gradle增加了对module-lint引用
dependencies {
lintChecks project(":module-lint")
}
执行lint
因为项目包含一些编译变体,编译变体指的时buildTypes做的一些打包区分,所以不能直接执行lint
终端进入项目目录
全量debug 下执行:
./gradlew lintDebug
全量release 下执行:
./gradlew lintRelease
执行单一module的检测:
./gradlew 模块名:lintRelease
//示例
./gradlew CommonSDK:lintDebug