Android自定义Lint踩坑实录 | 项目复盘

346 阅读6分钟

一、项目简介:

在日常开发中,很多的规范其实流于表面,靠的还是开发者的自觉和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 源码是怎么实现的,或许能为你打开新思路。

有待改进的点:

  1. 自定义 Lint 规则可以对整个项目进行检查,而对于老项目而言,很多历史代码没有线上问题,但是不符合我们自定义 Lint 的规范。这时候贸然去改动的风险较大,而且要求开发者对这块代码比较熟悉,这也无形增加了开发者的工作量。

  2. 针对上面的问题可以改进的点就是使用增量检查,限制只检查新提交代码,避免修改“祖传”代码。

  3. 对于团队重视的问题,可以考虑将严重程度调升至 Severity.ERROR,通过技术手段限制不规范的代码。作为开发者也应该首先关注高优先级问题,忽略低优先级的问题。对于高优先级的问题,必须确保优先解决。如果开发人员对所有优先级统一处理态度,那么分级其实也就失去了意义。

  4. 稳定运行后可以考虑发布到远程仓库或者本地仓库上,进一步可以考虑通过 plugin 实现lintOptions 等配置的使用统一标准。

  5. Lint 检查耗时的优化,检查出问题的统计记录等功能。

参考文章:

  1. Android 官方文章

  2. 美团外卖Android Lint代码检查实践


本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情