Lint能做什么
Android Lint是Google提供给Android开发者的静态代码检查工具。使用Lint对Android工程代码进行扫描和检查,可以发现代码潜在的问题,提醒程序员及早修正。 像诸如:拼写错误、兼容性错误(NewApi等)、魔法字符串检测、不必要的对象创建、方法使用等,都是可以以错误或其他方式提醒开发者进行修改。
Lint 是如何处理源文件
Lint是如何实现扫描分析
编译器对代码的处理流程
源文件->词语法分析-> 生成AST ->语义分析 -> 编译字节码
Lint使用了Lombok做抽象语法树的分析,根据我们给出的条件搜索出相应的node
Android Studio一些自带的lint工具
File > Settings > Edit > Inspections
为什么需要自定义
Google提供的Lint 检查规则不能解决我们在代码中遇到的问题:
1.魔法数字、字符串
2.lateinit的使用注意
3.一些可能存在崩溃的代码调用 或 影响性能的使用(Log、e.printStackTrace())等等其他的一些规范
此时就需要自定义lint 来满足我们需求
如何自定义lint-示例
几个比较重要的概念:
Issue: 检测过程中的问题,属性中包含:id、等级、扫描范围等
Detector:问题检测器,每一个Issue都需要对应一个Detector。利用特定的Api筛选节点,并根据条件上报问题
Scanner: 扫描器。针对不同类型的目标文件,对应着不同的扫描器
IssueRegistry:自定义Lint规则加载的入口,里面包含了需要注册的问题列表
了解完定义之后,我们以对Log方法的检测为例子,简单介绍下如何自定义Lint的实现
新建Java Library-lint_checker
配置Gradle
dependencies {
compileOnly 'com.android.tools.lint:lint-api:30.1.0-alpha02'
compileOnly 'com.android.tools.lint:lint-checks:30.1.0-alpha02'
}
创建Detector-问题检测器
Detector负责的是扫描代码,发现并上报问题。一些重要的Api可以参考LintApi
/**
* qinyafei
* 2021-06-30
*/
public class LogDetector extends Detector implements Detector.UastScanner {
public static final Issue ISSUE = Issue.create(
"LogUse", // 问题id
"避免直接使用android.util.Log", // 问题简要描述
"避免直接使用android.util.log,请使用xxx 代替", // 问题解释,解决方案
Category.SECURITY,// 问题所属类型-安全
5, // 问题优先级 0 - 10
Severity.ERROR, // 问题等级
new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)
);
/**
* 获取方法的筛选条件
* @return
*/
@Nullable
@Override
public List getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e");
}
/**
* 搜索到方法的回调
* @param context
* @param node
* @param method
*/
@Override
public void visitMethod(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
if (context.getEvaluator().isMemberInClass(method, "android.util.Log")) {
context.report(ISSUE, node, context.getLocation(node), "请勿直接调用Log,应该使用统一Log工具类"); // 问题上报
}
}
}
可以看LogDetector继承 Detector,实现了Scanner的接口,这里的UastScanner是对Java/kotlin 源文件进行扫描。根据接口实现的规则,我们自定义的LogDetector可以实现一个或者多个Scanner,具体的需要实现哪个接口,则取决于需要扫描的文件。Scanner的种类有以下这几种:
BinaryResourceScanner | 扫描二进制资源文件 |
---|---|
ClassScanner | Class文件扫描器 |
GradleScanner | 扫描Gradle脚本 |
ResourceFolderScanner | 资源文件扫描器 |
XmlScanner | 扫描XML文件 |
JavaScanner/JavaPsiScanner/UastSanner | 扫描源文件 |
OtherFileScanner | 扫描其他类型文件 |
代码中的getApplicableMethodNames()方法的返回值决定了哪些方法可以被检测到,visitMethod() 是方法被检测到之后的回调。检测&回调方法,都应该是成对的出现。类似的还有:
创建问题Issue
问题是由Detector检测并进行上报,代码如下:
public static final Issue ISSUE = Issue.create(
"LogUse", // 问题id
"避免直接使用android.util.Log", // 问题简要描述
"避免直接使用android.util.log,请使用xxx 代替", // 问题解释,解决方案
Category.SECURITY,// 问题所属类型-安全
5, // 问题优先级 0 - 10
Severity.ERROR, // 问题等级
new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)
);
需要注意的是,问题需要声明为final class 且必须由静态工厂方法创建,对应的参数解释如下:
1.id : 唯一值,应该能简短描述当前问题。利用Java注解或者XML属性进行屏蔽时,使用的就是这个id
2.summary : 简短的总结,描述问题而不是修复措施。
3.explanation : 完整的问题解释和修复建议。
4.category : 问题类别(部分):
- Security 安全
- Performance 性能
- Usability 可用性
- Accessibility 可访问性
- Correctness 正确性
5.priority : 优先级。1-10的数字,10为最重要/最严重。
6.severity : 严重级别:Fatal, Error, Warning, Informational, Ignore。
7.Implementation : 为Issue和Detector提供映射关系,Detector就是当前Detector。声明扫描检测的范围Scope,Scope用来描述Detector需要分析时需要考虑的文件集,包括:Resource文件或目录、Java文件、Class文件
各属性与报告中的对应关系如下:
声明问题注册器IssueRegistry
规则只有被注册,才会生效
/**
* 问题注册
* @author qinyafei
* @since 2021/6/30 16:13
*/
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
/**
* 返回问题列表
* @return
*/
@NotNull
@Override
public List getIssues() {
return new ArrayList() {{
add(LogDetector.ISSUE);
}};
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}
在build.gradle中声明 Lint-Registry
jar {
manifest {
attributes("Lint-Registry": "com.car300.lint_checker.IssueRegistry")
}
}
至此,自定义Lint的编码部分 就完成了,那么项目中要如何引用呢
不同引入方案
对于Demo工程,我们可以这样引入
lintChecks project(':lint_checker')
对于其他的项目,我们可以采用多种方式引入
方案2(不建议)
将jar拷贝到~/.android/lint中(我的路径为:C:\Users\Administrator\.android\lint)
缺点:所有工程都会生效
采用LinkedIn方案
将jar包放到一个aar中,这样就可以通过引入aar的方式实现
dependencies {
lintPublish project(':lint_checker')
}
这里只介绍 gradle plugin 3.4.0之后 的实现方式。新建一个aar模块,添加如下依赖
dependencies {
lintPublish project(':lint_checker')
}
在输出的aar包中,就可以看到lint.jar。在指定模块中引入aar,自定义的Lint就可以生效了
Lint检查时常用的一些设置(配置在需要检测的模块中)
android {
...
lintOptions {
// 不检查的问题
disable 'TypographyFractions','TypographyQuotes'
// 只检查的问题
checkOnly 'NewApi', 'InlinedApi'
// 检测出 错误 等级的问题是否终止编译
abortOnError false
// 只报告错误
ignoreWarnings true
}
}
...
常用命令行
gradlew clean lint 执行检查
lint --list 列出所有的问题id
lint --show 列出所有的问题,并给出详细解释
lint --check 只检查List列表中的问题id,其中List中的id 使用逗号分开
lint -w 只检查错误,忽略警告
lint --xml filename 创建一个xml的结果报告
lint --html filename 创建一个html 的结果报告
TODO
1.如何实现增量检查?
2.如何将检查增加到打包流程中?
参考博客
LintApi 使用 lint 检查改进您的代码 | Android 开发者 | Android Developers (google.cn)
【我的Android进阶之旅】Android自定义Lint实践_慕课手记 (imooc.com)
Lint - 美团技术团队 (meituan.com)
(4条消息) android lint check的学习和自定义以及lint语法_徒步行走于未知,爱上呼吸空气-CSDN博客
Writing Custom Lint Checks with Gradle | LinkedIn Engineering
添加构建依赖项 | Android 开发者 | Android Developers (google.cn)