一、项目简介:
在日常开发中,很多的规范其实流于表面,靠的还是开发者的自觉和code review,而单纯靠人力去检查代码其实并不保险,往往是出现问题才去弥补。为了保证团队开发规范,最近在项目中引入了自定义 Lint 静态代码检查。
Android Lint是 Google 提供给 Android 开发者的静态代码检查工具。使用 Lint 对 Android 工程代码进行扫描和检查,可以发现代码潜在的问题,提醒程序员及早修正。开发中最常见的代码块中的黄色警告区域就是 Lint 在起作用。
Android 原生提供了上百个 Lint API 供开发者使用,但是如果想要针对性对业务进行规范制定,就必须使用自定义 Lint。
二、项目背景:
由于公司项目比较老旧,也经历了很长时间的迭代,不同开发人员都有自己的代码习惯,导致了代码风格不一致,于是就想通过使用 Lint 规范代码。
之前有一个安全方面的需求就是把项目中的System.out.print
还有原生的 Log 类
替换成统一的日志打印工具类输出,还有就是开发人员使用 SharedPrefrences
时提示使用定义好的mmkv 工具类
,又或者是使用Glide
或其他第三方框架代替BitmapFactory
创建Bitmap
等。由于都是一些强业务相关性的 Lint 规范,因此这里只简单说明如何实现一个自定义 Lint,并且提示需要注意的坑 。
三、实践过程:
由于官方文档的缺乏,而且网络上很多中文博客的内容都是几年前的,再加上Lint API 的更新还算比较频繁,因此一路上可以说踩了不少坑。
自定义 Lint 首先要理解涉及到的主要API:
1. Issue:每个Issue代表一个Lint规则。
2. Detector:用于检测并报告代码中的Issue,每个Issue都要指定Detector。
3. Scope:声明Detector要扫描的代码范围,例如JAVA_FILE_SCOPE、CLASS_FILE_SCOPE、RESOURCE_FILE_SCOPE、GRADLE_SCOPE等,一个Issue可包含一到多个Scope。
4. Scanner:用于扫描并发现代码中的Issue,每个Detector可以实现一到多个Scanner。
5. IssueRegistry:Lint规则加载的入口,提供要检查的Issue列表。
下面就开始动手实践了
1.创建 java 工程
在根目录的build.gradle
文件中添加依赖
apply plugin: 'java'
dependencies {
//使用compileOnly避免依赖冲突
compileOnly "com.android.tools.lint:lint-api:27.1.2"
//引入 lint-checks ,就可以在Android Studio中查看源码了。
compileOnly "com.android.tools.lint:lint-checks:27.1.2"
}
这里要注意第一个坑,如果 Gradle 插件的版本是X.Y.Z,那么Lint库的版本需要是X+23.Y.Z。例如项目中的 Gradle 插件版本为4.1.2,那么我引入的Lint版本为27.1.2,如果对应不上你的 demo 可能会跑不起来~
2.创建 Detector & ISSUE
我们首先要实现一个 Detector,用于扫描代码,发现问题并报告出来。
而 ISSUE 表示的是对问题的描述,包括优先级,问题详情,修复建议,声明检查的范围等等的信息。值得一提的是,如果检查到严重程度为 ERROR 的错误,会中止编译流程。
而 Dector 还需要实现 Scanner 接口表示扫描的类型,Scanner 经历了 JavaScanner -> JavaPsiScanner -> UastScanner 的版本更替,也就是为什么很多时候参考网上博客的例子跑不起来的原因,当然我们也推荐使用最新的 UastScanner , 因为它还支持 kotlin。
这里提供一个 log 检查的 Dector 实现示例:
public class LogUtilDetector extends Detector implements Detector.UastScanner{
//自定义ISSUE
public static final Issue ISSUE = Issue.create(
"LogUse", //唯一id
"避免使用Log或者System.out.println", //问题详情
"请使用项目中封装好的LogUtil,以便于统一管理log打印", //修复建议
Category.SECURITY, //问题种类
5, //优先级
Severity.WARNING, //严重程度
new Implementation(LogUtilDetector.class, //对象实例
Scope.JAVA_FILE_SCOPE) //作用域
);
//限定检测类型
public List<String> getApplicableMethodNames() {
return Arrays.asList("d", "e", "i", "v", "w");
}
//具体处理回调
public void visitMethod(JavaContext context, JavaElementVisitor visitor, PsiMethodCallExpression node, PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "android.util.Log")) {
String message = "请使用项目中封装好的LogUtil";
context.report(ISSUE, node, context.getLocation(node), message);
}
}
}
3. 注册 Detector
创建一个 RegistryActivity 继承 IssueRegistry ,然后注册上一步中实现的 LogUtilDetector。这里就是自定义 Lint 工具的入口。
public final class RegistryActivity extends IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(
LogUtilDetector.ISSUE
);
}
}
4. 引入自定义 Lint
在你的 Lint module 的 build.gradle 文件中添加如下代码:
jar {
manifest {
attributes("Lint-Registry-v2": "RegistryActivity")
}
}
上面的 key Lint-Registry-v2
是固定的,后面的 value 需要根据的你实际的 IssueRegistry 类命名去改变。
然后在你需要代码检查的 module 的 build.gradle 中添加:
dependencies {
lintChecks project(":lint_lib")
}
到这里就可以通过命令行执行自定义 Lint 任务了。
最后的最后,如果在哪个 module 中使用 compileOnly 添加 Lint 依赖,就只会对该 module 进行代码检查,并且不会打入 release 包中。
四、总结思考:
总结一下,基于自定义 Lint 可以做到对项目中的静态代码规范检查,但是想要在根本上提高代码质量,还是要靠开发者提高规范意识。例如总结规范的开发文档,完善测试流程等。
Android 提供的原生 Lint 也非常值得我们学习。如果你有想法却没有很好的实现思路应该怎么办:去看看 BuiltinIssueRegistry 类中内置的 Lint 源码是怎么实现的,或许能为你打开新思路。
有待改进的点:
-
自定义 Lint 规则可以对整个项目进行检查,而对于老项目而言,很多历史代码没有线上问题,但是不符合我们自定义 Lint 的规范。这时候贸然去改动的风险较大,而且要求开发者对这块代码比较熟悉,这也无形增加了开发者的工作量。
-
针对上面的问题可以改进的点就是使用增量检查,限制只检查新提交代码,避免修改“祖传”代码。
-
对于团队重视的问题,可以考虑将严重程度调升至
Severity.ERROR
,通过技术手段限制不规范的代码。作为开发者也应该首先关注高优先级问题,忽略低优先级的问题。对于高优先级的问题,必须确保优先解决。如果开发人员对所有优先级统一处理态度,那么分级其实也就失去了意义。 -
稳定运行后可以考虑发布到远程仓库或者本地仓库上,进一步可以考虑通过 plugin 实现lintOptions 等配置的使用统一标准。
-
Lint 检查耗时的优化,检查出问题的统计记录等功能。
参考文章:
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情