文章目录

  • 一、Lint基本使用介绍
    • 1. 界面操作
    • 2 命令行运行
  • 二、自定义Lint规则
    • 第一步:创建一个Lint规则库
      • 1.1 定义Issue
      • 1.2 定义Detector
      • 1.3 定义并配置Registry
    • 第二步:创建一个Android库
    • 第三步:在项目中添加自定义Lint规则
  • 三、Lint检查的配置
  • 四、总结

LintAndroid Studio里面提供的一个代码检查工具,相信大多数Android开发者都用过或了解过Lint,它可以用来对项目做一些基本的但却非常有必要的代码检查,以帮助开发者提升程序的可靠性和性能,以及提高程序的可维护性。比如:XML中存在无用的命名空间代码中调用了已被弃用的API多余的装箱操作等,Lint通通可以一次检查出来并给予提示和修改建议。

然而这样的检查仅限于检查Lint支持的问题,并且需要开发者主动操作进行检查。那我们可不可以自定义Lint检查规则呢?可不可以让Lint在我们输入代码的时候就给出错误提示呢?或者在编译项目的时侯自动检查呢?答案是:可以的。

这篇文章我们将着重讲解自定义Lint检查规则Lint自动检查配置。但在开始之前,不妨先简单介绍一下Lint的基本使用方法。

一、Lint基本使用介绍

有两种方式可以运行Lint检查。

1. 界面操作

在 Android Studio中选择 Analyze -> Inspect Code,会弹出如下对话框:

在这个对话框中有两个可配置项:

  • Inspection scope:选择检查的范围,可以选择整个项目检查或是只检查某些模块,也可以自定义检查范围。
  • Inspection profile:用来配置检查的问题项,这里点进去可以看到Lint内置的所有检查项。比如下图中用红色框标出来的,表示它支持检查Handler引用可能导致内存泄漏的问题。如果想取消某项检查,只需取消勾选就行。

点击Ok按钮,即启动Lint检查。

检查完成之后,Android Studio底部将弹出一个检查结果对话框,列出所有检查到的问题及所在文件位置,点击即可直接打开文件进行修改。如下图所示:

2 命令行运行

Terminal中输入命令行

gradlew lint

即可启动Lint检查。检查完成后会生成一份名叫 lint-results.html的文档,列出所有检查到的问题。

以上两种运行Lint的方式,我个人偏向于使用第一种,因为比较直观,而且检查完成后可以直接定位修改问题。

更详细的关于Lint的使用方法,请参考官方文档 Improve your code with lint checks


二、自定义Lint规则

前面我们介绍了如何运行Android Studio自带的Lint检查,以及介绍了如何查看或者勾选/取消其自带的检查规则。
自定义Lint检查规则,其实就是把自己写的规则加入到Lint里面去。这个过程有些麻烦,下面我们分步来讲解。

请注意:在不同版本的Android Studio里面,Lint检查的版本有所不同。这篇文章的内容只针对 Android Studio 4 以上的版本。

第一步:创建一个Lint规则库

首先,我们要单独建一个 Java库来写自定义的Lint规则。请注意,是 Java Library,不是 Android Library

要自定义Lint检查规则,首先需要导入两个Lint官方库,导入方式为 compileOnly,因为Lint检查只在编译时有效。

dependencies {    compileOnly 'com.android.tools.lint:lint-api:27.1.1'    compileOnly 'com.android.tools.lint:lint-checks:27.1.1'
}

注意:这两个库的版本号必须与Android Studio里面Lint的版本号对应起来,对应关系是 Gradle plugin的版本号加上23要等于这两个库的版本号。例如这两个库的版本号是 27.1.1,那么com.android.tools.build:gradle的版本号就应该是 4.1.1,如下:

dependencies {    classpath "com.android.tools.build:gradle:4.1.1"
}

版本号如果不匹配,自定义的Lint规则将无法生效。

导入合适的库之后就可以开始自己写检查规则了,自定义规则有三个重要的类:

  • Detector :用来寻找和定位代码中的问题。我们需要创建自己的探测器,来检查代码;
  • Issue:用来定义和描述问题;
  • IssueRegistry:问题注册器,通过该注册器,我们可以把自定义的问题注册到Android Studio里面。

我们以一个例子来详细介绍上面这三个类以及自定义Lint规则的整个过程。

假设我们的项目中封装了一个打印日志的类,名叫 LogUtil,我们希望项目中所有要打印日志的地方都使用该类提供的方法。如果有人直接调用了 android.util.Log 来打印日志,我们就让Android Studio报错,并提示他/她使用已经封装好的 LogUtil类。

1.1 定义Issue

Issue所定义的就是前面的 Inspection profile列表里面的问题项。可以用如下方法创建一个Issue:

public final static Issue LOG_ISSUE = Issue.create("Log_Issue",        "Do not use Log",        "Please Use `LogUtil` instead of `android.util.Log`",        Category.CORRECTNESS,        6,        Severity.ERROR,        new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE));

不妨把前面的图片在这里再放一次,可以对照图片来理解各参数的作用:

  • 第1个参数为Issue的id;
  • 第2个参数为Issue的简单描述,可以理解为Issue的标题;
  • 第3个参数是Issue的详细描述,通常用来提示如何修改该问题;
  • 第4个参数是Issue的类别,如图中所示,可以分为各种类别;
  • 第5个参数是Issue的优先级,范围是1到10
  • 第6个参数表示Issue的严重程度,比如ERROR表示出错,必须修改;WARNING表示提醒,可能存在问题;
  • 第7个参数用来对应Issue和Detector,每个Issue都要有与之对应的Detector,表示用该Detector来查找该Issue。其中的Scope表示扫描的范围,Scope.JAVA_FILE_SCOPE表示只扫描Java源文件。
1.2 定义Detector
public final class LogDetector extends Detector implements Detector.UastScanner{@Override    public List<Class<? extends UElement>> getApplicableUastTypes() {        return Collections.singletonList(UCallExpression.class);    }    @Override    public UElementHandler createUastHandler(@NotNull JavaContext context) {        return new UElementHandler(){            @Override            public void visitCallExpression(@NotNull UCallExpression node) {if(context.getEvaluator().isMemberInClass(node.resolve(),  "android.util.Log")){                           context.report(LOG_ISSUE, context.getLocation(node),LOG_ISSUE.getExplanation(TextFormat.TEXT));                }          }       };   }
}

这个比较复杂,不如先上代码。

这里我们定义了一个 LogDetector,它继承自Detector,并且实现了其中的UastScanner接口。UAST 的全称是 Unified Abstract Syntax Tree,可翻译为 统一抽象语法树,即以一种特定的树状结构来描述代码。要在这篇文章里详细介绍UAST是不可能的,我们可以把它类比为一个XML文件的结构,通过解析XML,我们可以获得XML文件所描述的全部信息。同样,通过解析UAST,我们可以获得代码文件的全部信息,比如里面定义的类的信息、类里面的方法的信息、方法里面的代码语句等等。

我们主要关注两个方法:

  • getApplicableUastTypes,该方法返回Detector扫描的UAST节点类型,我们这里返回了 UCallExpression.class,表示LogDetector只扫描方法调用,就是当扫描代码的时候,只关心方法调用的语句。
  • createUastHandler,需要返回一个自定义的 UElementHandler,用来处理UAST。这里我们要重写UElementHandler的 visitCallExpression方法,为什么要重写这个方法呢,因为我们在 getApplicableUastTypes 中指定了该Detector只扫描方法调用。这两个方法是相互对应的,在 getApplicableUastTypes方法中指定了什么类型,就需要重写 UElementHandler里面指定的visit方法。

简单举几个例子:

getApplicableUastTypes中返回的类型 需要重写的UElementHandler方法
UCallExpression.class visitCallExpression
UField.class visitField
UAnnotation.class visitAnnotation

在LogDetector的 visitCallExpression方法中,我们检测如果被调用的方法来自于 android.util.Log 类,就上报前面定义好的 LOG_ISSUE

补充说明:Detector类里面不只有UastScanner接口,也还有其它各种Scanner接口,其作用各不相同,这里列举其中几个作简单介绍;

  • UastScanner: 用来扫描java/kotlin源文件
  • XmlScanner: 用来扫描XML文件
  • GradleScanner: 用来扫描Gradle文件

可见,我们不仅能自定义Java/Kotlin代码的Lint检查规则,也可以对资源文件甚至Gradle文件制定检查规则。

对于Detector里面的检测方法如何写,其实挺不好讲的,因为我自己也没有完全弄明白。官方也说了该API目前只是一个Beta版,甚至连正式的API文档都没有。但是呢还是有办法可以摸索的,比如可以调用 System.out.println()方法把节点信息打印出来,看哪些信息是自己需要的;也可以多看别人是如何写的,从中借鉴学习;甚至,因为Android Studio内置了许多Lint检查规则,所以可以通过读源码学习官方的写法。在这里提供一个官方Detector源码入口。

1.3 定义并配置Registry

Registry就是用来把我们自定义的规则注册到Android Studio的Lint检查规则里面去。

不如先放代码:

public final class CustomIssueRegistry extends IssueRegistry {    @NotNull    @Override    public List<Issue> getIssues() {        return Collections.singletonList(CustomIssues.LOG_ISSUE);   }    @Override    public int getApi() {        return ApiKt.CURRENT_API;    }
}

很简单,最重要的只有一个方法,即 getIssues(),通过这个方法返回自定义的全部Issue。geApi方法用来返回当前Lint的API版本号,这是一个固定写法,通常只需要返回 CURRENT_API即可。

写完Registry之后,要在与 Java文件夹 同级的文件夹下创建一个 resources资源文件夹,然后在resources文件夹下创建 META-INF文件夹,然后再在META-INF文件夹下创建 services文件夹,然后在services文件夹中创建一个名为 com.android.tools.lint.client.api.IssueRegistry 的文件,然后把自定义的Registry的全路径名写在里面。

自定义的Lint规则库的结构如下图所示:

其中 com.android.tools.lint.client.api.IssueRegistry 文件中的内容为:

com.crx.lintrules.CustomIssueRegistry

第二步:创建一个Android库

Android库新建完成后,无需在里面添加任何代码,它只需要依赖于我们前面定义好的Lint规则库。依赖方式如下:

dependencies {    lintPublish project(':lint_rules')}

配置好依赖,编译完成之后Lint规则库会产生一个 lint.jar包,Android库会接收该jar包并把它包含在自己编译生成的aar文件中。之后,任何依赖于此Android库的其他项目都将自动执行自定义的Lint检查。

第三步:在项目中添加自定义Lint规则

在项目里引用在第二步中创建好的Android库,该项目就会自动执行在Lint规则库里定义的检查。假如Android库的名字为 lintlibrary,那么依赖方式如下:

implementation project(':lintlibrary')

编译好项目之后,打开 Analyze -> Inspect Code -> Inspection profile,可以在里面找到我们自定义的Lint规则,如下:

现在,如果我们在代码里调用了 Log类的方法,Android Studio就会自动报错,如下:

由于在封装的 LogUtil类里面也调用了Log类方法,我们需要在LogUtil类顶部添加注解 @SuppressLint("Log_Issue"),该注解表示在该类中取消对 Log_Issue的Lint检查。

至此,自定义Lint检查算是完成了,如果有代码违反了我们自定义的规则,Android Studio 就能即时提醒。

但是这样的提醒并不是强制的。即使出现了代码出错的提醒,项目仍然可以顺利构建并打包。而我们希望的是在项目构建之前必须通过Lint检查。下一节我们就来讲解如何让项目在构建之前自动运行Lint检查,如果发现错误就中断构建。


三、Lint检查的配置

让项目在构建之前自动进行Lint检查有两种方式:

第一种方式是通过 Edit Configurations手动添加。

但是这里就不详细介绍了,在本文末尾参考文章列表的第一篇《Run lint when building android studio projectsAsk Question》里有详细的添加步骤。该方法的缺点是这种添加方式只对个人有效,没办法做到一次配置对所有参与该项目的人都产生效果。

那我们直接讲第二种配置方式,在App Module的 build.gradle文件里配置。

如下:

android {··· ···lintOptions{    abortOnError true}applicationVariants.all { variant ->    def lintTask = tasks["lint${variant.name.capitalize()}"]            variant.getAssembleProvider().configure(){        it.dependsOn lintTask    }}
}

lintOptions选项表示如果lint检查遇到Error,就停止构建。
第二个配置项是让assemble任务依赖于lint检查,即每次执行assemble任务时都会启动lint检查。

配置好之后,以后项目的每一次构建,都会自动执行Lint检查,如果遇到Error,就会终止构建。当然,运行Lint检查是需要消耗时间的,不可避免地会造成项目构建速度变慢。

完整的项目代码已上传到github,欢迎取用LintCustomRuleSample


四、总结

前面已经说过,自定义Lint规则的API仍然是Beta版,也没有正式的文档,因此还存在不少坑。受限于个人水平,这篇文章只作一个初步介绍。如果想进一步了解,需要自己多花时间去摸索,也要多读其他人写的相关文章。我非常建议想作进一步了解的人读一下参考文章的第二篇《美团外卖Android Lint代码检查实践》和第三篇《Enforcing Team Rules with Lint: Detectors

自定义Lint检查规则相关推荐

  1. Android自定义Lint检查-CustomLint

    仓库地址 背景 1.大多代码规范都浮于表面,全靠开发自觉和Code Review来保障导致很多遗留的老大难问题. 2.基础模块很多规范只能通过注释或者文档来约束. 相关文档 安卓自定义lint实现 L ...

  2. Android Studio 3.0+ 版本的自定义lint检查

    Android Studio 3.0+ 版本的自定义lint检查 定义LintModule 开发lint规则 lint库依赖 新建log检查规则 LogDetactor IssueRegistry 注 ...

  3. Android Lint 检查规则的定制(基本篇)

    本人博客原文 英文原文: http://tools.android.com/tips/lint/suppressing-lint-warnings http://tools.android.com/r ...

  4. 【我的Android进阶之旅】Android自定义Lint实践

    背景 2017年8月份的时候,我在公司开始推广Lint.FindBugs等静态代码检测工具.然后发现系统自带的Lint检测的Issue不满足我们团队内部的特定需求,因此去自定义了部分Lint规则.这个 ...

  5. Android自定义Lint实践

    Android Lint是Google提供给Android开发者的静态代码检查工具.使用Lint对Android工程代码进行扫描和检查,可以发现代码潜在的问题,提醒程序员及早修正. 为保证代码质量,美 ...

  6. Android 自定义 Lint 实现静态代码扫描工具

    文章目录 关于静态代码扫描工具 Lint的简单使用 一.Lint 与 IDE 的结合使用 二.Lint 与 gradle 命令的结合使用 具体位置如下图: 生成的HTML在浏览器打开如图: 自定义 L ...

  7. 自动规避代码陷阱——自定义Lint规则

    目录 一.Lint是什么? 二.Lint的使用 三.为什么要使用自定义Lint规则? 四.新建module 五.在lintjar中定义规则 六.配置lintjar中gradle 七.配置LintAar ...

  8. 自定义 Lint 规则简介

    2019独角兽企业重金招聘Python工程师标准>>> 上个月,笔者在巴黎 Droidcon 的 BarCamp 研讨会上聆听了 Matthew Compton 关于编写自己的 Li ...

  9. 使用Lint检查提高代码质量

    使用Lint检查提高代码质量 1.概述 2.代码中使用标记 2.1 概述 2.2 在工程中使用标记 2.3 一些标记的使用 2.3.1 Nullness标记 2.3.2 资源标记 2.3.3 线程标记 ...

最新文章

  1. 计算机视觉常用图像数据集标记平台
  2. 【C++】random随机数与【C++11】/rand()和srand()的用法
  3. 一个有效的OKR是什么样?
  4. 计算机使用DHCP动态分配参数,某单位采用DHCP进行IP地址自动分配,用户收到()消息后方可使用其中分配的IP - 信管网...
  5. 关于通信matlab 仿真,关于MATLAB在通信仿真系统中的运用
  6. python中number函数_Python 数字(Number)
  7. Python 字典(Dictionary) values()
  8. 物联网全面崛起 LED企业大有可为
  9. android开源SlidingMenu使用
  10. 【Spring】学习SpringAOP
  11. idf逆文档频率为什么要用log??
  12. 高速ADC模拟输入接口设计
  13. Guava---Joiner
  14. 计算机是什么信号转换为什么信号,模拟信号转化为数字信号的原理是什么
  15. python中 s是什么意思_python中字符串 s[ : -1]是什么意思?
  16. 计算机内存条能装几个,电脑能装几个内存条_一般电脑插几个内存条
  17. Android 设置边距总结
  18. maximo安全组没有添加用户的权限
  19. 下载的中文文件名乱码,如何转码
  20. 君正X1830芯片性能和处理器介绍

热门文章

  1. 深信服售前产品经理面试准备材料(更新ing)
  2. 使用dreamweaver制作采用DIV+CSS进行布局——美食甜品店铺加盟企业HTML静态网页 ——学生美食网页设计作品静态HTML网页模板源码
  3. BUUCTF [GXYCTF2019]Ping Ping Ping 1
  4. 深信服实习面经11_02
  5. Java 基于mail.jar 和 activation.jar 封装的邮件发送工具类
  6. matlab笔记 与excel表格的数据交互—xlsread和xlswrite函数
  7. 虚拟机一直光标闪,进不去,解决方法之一。
  8. 用pycharm创建数据库sqlites3表格,但是打开并不显示
  9. 学习学习学习学习学习学习
  10. border-image