自定义Lint
Android Lint 是由 Android SDK 提供的一种静态代码检测工具,用于检测 Android 项目的代码质量,帮你查出可能发生的bug以及可以优化的代码
此文档只针对
AS 3.0+
Gradle 3.3+
Android Plugin Version 3.0.1+
本文参考以下项目
googlesamples的android-custom-lint-rules库
GavinCT(美团)的MeituanLintDemo
LintOptions配置
如果将 abortOnError 设置为true,自定义Lint很容易出现编译失败,此时检查message窗口将不必要的检查进行忽略禁用十分必要,例如 disable 'MissingTranslation' 此配置将忽略没有对strings.xml进行翻译报的错误。其他配置项查看下面各注释
各个配置含义
android {
lintOptions {
// 设置为 true时lint将不报告分析的进度
quiet true
// 如果为 true,则当lint发现错误时停止 gradle构建
abortOnError false
// 如果为 true,则只报告错误
ignoreWarnings true
// 如果为 true,则当有错误时会显示文件的全路径或绝对路径 (默认情况下为true)
//absolutePaths true
// 如果为 true,则检查所有的问题,包括默认不检查问题
checkAllWarnings true
// 如果为 true,则将所有警告视为错误
warningsAsErrors true
// 不检查给定的问题id
disable 'TypographyFractions','TypographyQuotes'
// 检查给定的问题 id
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// * 仅 * 检查给定的问题 id
check 'NewApi', 'InlinedApi'
// 如果为true,则在错误报告的输出中不包括源代码行
noLines true
// 如果为 true,则对一个错误的问题显示它所在的所有地方,而不会截短列表,等等。
showAll true
// 重置 lint 配置(使用默认的严重性等设置)。
lintConfig file("default-lint.xml")
// 如果为 true,生成一个问题的纯文本报告(默认为false)
textReport true
// 配置写入输出结果的位置;它可以是一个文件或 “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'
}
}
此部分内容粘贴自LintOptions
此外,还可以通过 lint.xml 文件进行lint规则的配置,如果项目工程中没有此文件自行创建在项目module根目录下即可,具体配置参考:
自定义Lint方案
针对不同的自定义lint方案,其中LinkedIn方案可行性最高,将lint.jar放入AAR包中,项目依赖AAR从而进行自定义lint开发,其中lint.jar只对当前项目工程有效。
LinkedIn提供的解决方案原文
项目详细配置
创建java工程,配置Gradle
提供lint.jar,自定义lint规则在此工程中进行
apply plugin: 'java-library'
def lintVersion = "26.0.0-beta5"
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compileOnly "com.android.tools.lint:lint-api:$lintVersion"
compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
testCompile "junit:junit:4.12"
testCompile "com.android.tools.lint:lint:$lintVersion"
}
jar {
manifest {
attributes("Lint-Registry-v2":"com.appchina.android.lint.core.AppChinaIssueRegistry")
}
}
创建AAR,配置Gradle
apply plugin: 'com.android.library'
...
dependencies {
lintChecks project('lintCoreLibrary') //lintCoreLibrary为上面的java工程名
}
...
此处 lint 还不会生效,需要将 aar 包引用到主项目,两种方式:
1.手动编译出 .aar 集成至主项目,即放至 libs 目录并进行 aar 依赖
2.还上传至 JCenter,进行远程依赖,更新更为方便
创建Detector
下面是系统ToastUtilsDetector的源码
public class ToastUtilsDetector extends Detector implements Detector.UastScanner {
private static final Class extends Detector> DETECTOR_CLASS = ToastUtilsDetector.class;
private static final EnumSet DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;
private static final Implementation IMPLEMENTATION = new Implementation(
DETECTOR_CLASS,
DETECTOR_SCOPE
);
private static final String ISSUE_ID = "ToastUseError";
private static final String ISSUE_DESCRIPTION = "You should use our{ToastUtils}";
private static final String ISSUE_EXPLANATION = "You should NOT use android.widget.Toast directly. Instead you should use ToastUtils we offered.";
private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;
private static final int ISSUE_PRIORITY = 9;
private static final Severity ISSUE_SEVERITY = Severity.ERROR;
private static final String CHECK_PACKAGE = "android.widget.Toast";
public static final Issue ISSUE = Issue.create(
ISSUE_ID,
ISSUE_DESCRIPTION,
ISSUE_EXPLANATION,
ISSUE_CATEGORY,
ISSUE_PRIORITY,
ISSUE_SEVERITY,
IMPLEMENTATION
);
@Override
public List getApplicableMethodNames() {
return Arrays.asList("makeText", "show");
}
@Override
public void visitMethod(@NonNull JavaContext context, @NonNull UCallExpression node, @NonNull PsiMethod method) {
if (!context.getEvaluator().isMemberInClass(method, CHECK_PACKAGE)) {
return;
}
List args = node.getValueArguments();
UExpression duration = null;
if (args.size() == 3) {
duration = args.get(2);
}
LintFix fix = null;
if (duration != null) {
String replace;
if ("Toast.LENGTH_LONG".equals(duration.toString())) {
replace = "ToastUtils.showLong(" + args.get(0).toString() + ", " + args.get(1).toString() + ");";
} else {
replace = "ToastUtils.showShort(" + args.get(0).toString() + ", " + args.get(1).toString() + ");";
}
fix = fix().name("Replace with ToastUtils")
.replace()
.with(replace)
.build();
}
if (fix != null) {
context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION, fix);
}
}
}
LintFix 提供快捷修复错误,如上面代码中快捷替换错误代码片段;支持使用正则表达式,具体api可查看LintFix源码,使用比较简单;
如果不需要快捷修复,可以使用JavaContext.report()的其他方法,此时只会进行代码标注(红(Error) / 黄(Waring))提示,Alt+Enter并不会出现快捷修复的提示
自定义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
返回需要检查的父类名列表,此处需要类的全路径名
11.visitClass
检查applicableSuperClasses返回的类
ISSUE
ISSUE在每个Detector中定义,lint检查到相关项将ISSUE报告出来,示例:
public static final Issue ISSUE = Issue.create(
"ListView",
"AppChinaLint:Replace 'ListView' with 'RecyclerView'",
"RecyclerView is better than ListView",
Category.CORRECTNESS, 6, Severity.WARNING,
new Implementation(ListViewDetector.class, Scope.JAVA_FILE_SCOPE));
在此处可自定义错误描述、安全级别(WARNING、ERROR等)
Category
Category表示lint结果在IDE中的分类,系统已有类别:
Lint
Correctness (incl. Messages)
Security
Performance
Usability (incl. Icons, Typography)
Accessibility
Internationalization
Bi-directional text
此外还可以自定义Category,示例:
public class MTCategory {
public static final Category NAMING_CONVENTION = Category.create("命名规范", 101);
}
IssueRegistry
提供需要被检测的Issue列表,示例:
@Override
public synchronized List getIssues() {
return Arrays.asList(
LogDetector.ISSUE,
ListViewDetector.ISSUE,
HashMapForJDK7Detector.ISSUE,
ToastUtilsDetector.ISSUE,
SampleCodeDetector.ISSUE,
FindViewDetector.ISSUE,
BaseActivityDetector.ISSUE,
BaseFragmentDetector.ISSUE
);
}