lint是用于检查android代码,资源的工具/规则,无论是使用eclipse(adt)或是as,环境中都已经默认集成了大量的检查规则,诸如版本兼容检查,优化检查,可能异常检查等,具体可以参考以上链接。
lint可以用于以下几方面的检查:
1. 命名规范(包括代码及资源等);
2. 定点上下文检查(由特定条件触发,用于检查上下文是否符合规范)
3. 代码使用规范(如需要用自定义log工具替代默认log等)
lint除了默认的规则外,还支持自定义规则,本文旨在介绍lint的自定义开发。
本文对应示例代码地址:https://github.com/demonk/lint-sample
自定义lint产出的是一个jar包,一般的自定义lint工程(可以参考示例中的lib工程),需要先引用lint的两个依赖
1 | dependencies { |
这两个依赖工程里可以有源码参考。
自定义的开始需要先定义一个入口,入口是一个com.android.tools.lint.client.api.IssueRegistry的子类,继承后,需要实现一个getIssues的方法
1 | public class CustomIssueRegistry extends IssueRegistry { |
对于每一个规则来说,都可以定义一个ISSUE来描述问题,这个ISSUE定义了需要扫描的问题,提示,严重级别,以及与之关系的具体扫描规则实现映射等,对于需要添加的规则,可以在这里添加,最后返回一个List列表。
定义好Registry后,需要在manifest中指定这个入口,这个需要在build.gradle里定义:
1 | jar { |
由于lint规则是作为一个jar被加载执行的,所以需要在manifest里指定入口。
指定完入口后,就可以去定义一条条的规则了,以下以检查代码里有没有使用android.util.Log为例,假如代码里使用了则直接提示错误。
在示例代码里,我把所有的扫描规则都定义在cn.demonk.lint.rules下,每一个规则其实都是Detector的子类,我们在定义一条规则时,除了需要继承Detector(或其已有的子类,源码里有提供一些默认的模板),还需要声明实现了哪种Scanner方法(见后),不同的Scanner的作用不一样,有一些是用于扫描java源码的,有些是用于扫描class的,有些是用于扫描资源的,这里我们使用ClassScanner,表示需要在classes里作扫描。
如上面如说,定义一条规则需要先定义一个ISSUE,代码如下:
1 | public static final Issue ISSUE = Issue.create("LogChecker", |
从上到下的意义为:
“LogChecker”:每一个issues都需要有一个id,这个就是id;
“Please use …..”: 简单的提示;
“You shouldn’t call Log ….”:更为详细的提示;
Category.MESSAGE: 问题分类;
5: 严重级别,1-10;
Severity.ERROR: 错误级别;
new Implementation(…): 这个是用于关联Detector与问题类别
最后一个参数我猜想是这样的,Scope定义了扫描的范围,如扫描源码还是资源,是CLASS还是MANIFEST,当扫描到对应的文件时就将事件发送到绑定的Detector类里,看样子是个观察模式。
Scope里可以定义一种或者多种(集合)的scope,如果只需要检查java源码的就只需要JAVA_FILE,但如果要检查源码和class的,也可以自定义一个EnumSet
注册定义好一个问题分类后,就可以开始定义扫描规则了,由于我们是需要检查是否有使用android.util.Log,我们可以从监控方法的调用开始着手。
点开ClassScanner的接口声明,关于方法调用的有两个声明:
1 | List<String> getApplicableCallNames(); |
getApplicableMethodNames()返回需要监控的方法名,现在我们要监控Log的常用方法,所以在Detector里可以重载这个方法,并返回Log的常用方法名:
1 | public List<String> getApplicableCallNames() { |
如何,当执行到以这些为方法名的方法时,就会触发检查。
注意,这个方法名并没有针对类或者对象,是所有叫这个名的都会触发。
当检查到这个方法名后,就可以在checkCall里作检查了,checkCall,方法如其名。
1 | public void checkCall(ClassContext classContext, ClassNode classNode, MethodNode methodNode, MethodInsnNode methodinsnNode) { |
四个参数目前解析如下:
ClassContext: 执行此方法时的上下文,有时从此上下文中获取一些数据
ClassNode: 类的AST结点(关于AST后续后讲)
MethodNode: 方法的AST结点
MethodInsnNode: 执行时方法时的一些状态,可以打开此类查看,还比较简单
这里我们只需要直接判断这个方法是否属性android.util.Log就可以了,使用methodinsnNode的owner可以获取到这个方法的所属,由于我们检查的是class,所以需要使用斜线来划分包。
当判断通过后,表示捕获到的这个方法执行就是使用android.util.Log来触发的,需要报告一个错误。报告错误比较简单,直接使用context的report方法报告即可,从上而下参数解析:
ISSUE:当前Detector定义的ISSUE
methodNode: 方法的AST结点
methodinsnNode: 执行方法的状态对象
getLocation:获取出错的位置
“…”:提示
然后在测试demo中随便打上一句直接使用Log输出日志的代码就行了。
最后,需要将生成的lint.jar放置到特定的位置(~/.android/lint/ ),文件名随意,然后执行:
1 | gradle :app:lint |
然后就会执行构建并执行lint检查,最后会生成一份报告。
对于as(gradle)来说,可以创建一个aar项目,然后主工程(测试工程,app)在编译lint时将新编译的lint复制到build/intermediates/lint,gradle就会自动检查,这个涉及到gradle的打包脚本,不多说,可以直接参考示例代码。