自定义lint
来源:互联网 发布:做淘宝赚钱吗 编辑:程序博客网 时间:2024/06/09 22:44
要做自定义的lint检查,首先要理解一个Abstract Syntax Tree(简称AST)抽象语法树的概念。它是一个java程序完成编译所必要的前期工作,将java代码的语法按照树结构解析存储起来。相关讲解可见
(https://juejin.im/entry/576b61162e958a0078c637fb)。而我们要做的lint检查就是基于这个抽象语法树的检查。
下面,我从实现一个自定义的lint(检查变量名是否按要求命名)来讲解一下怎么实现。
首先,我们需要做的是一个准备的工作,就是引入lint包:(总不能安装studio这样的工作也要我放在一个步骤里吧,所以这些感觉不那么核心的步骤我们就不当做主要步骤来归档了,包括后面的使jar生效等步骤。但这些都是必须的,只不过于其实现原理没那么核心)
我们创建一个java library的module,在其build.gradle文件中添加:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.tools.lint:lint-api:25.3.3'}
我用的是当前最新的版本25.3.3,我注意到以前一些版本跟这个版本的实现方法和小要求不大一样,如果不是这个版本的,我建议换成最新的这个来做吧。而且我感觉这个版本的api比以前那套确实好用一点点,虽然还不是很清晰。。。
接着,我们就可以开始主要的步骤了。
总的来说,实现过程主要有两步:
一. 使用gradle注册一个lint检查器
二. 实现这个lint检查器
注册检查器
我们可能以为直接就可以把检查器注册进构建文档里,其实不是,我们需要先添加一个“注册器”,然后才在注册器里面申明要执行的检查器。这可能是为了解耦吧,然而我觉得这个过程有点多余,完全可以直接设计成注册检查器的方式,因为build文件里面本来就是一个注册的角色了,而且最终也只是为了注册检查器,又何必再加多一个注册器呢。至少从目前的功能要求上讲,这个注册器有点多余。我们接着按照要求来做:
首先,在gradle文件中加入这样一个配置:
jar { manifest { attributes("Lint-Registry": "com.ives.lint.WordSpellRegistry") }}
显然,这似乎是把一个注册器注册给了gradle,但这个注册器我们似乎还没有,那么下一步我们就是要添加一个这样的注册器。
这一步只需要在上面所写的包下添加一个相应类名的java类WordSpellRegistry,这个类继承于IssueRegistry,并且实现里面的getIssues方法:
public class WordSpellRegistryextends IssueRegistry { public WordSpellRegistry() {//!!必须要有无参构造器 } @Override public List<Issue> getIssues() { return Arrays.asList(//这几个元素后面接着讲 WordSpellDetector.ISSUE, MessageObtainDetector.ISSUE, ParameterLengthConstraintDetector.ISSUE ); }}
getIssues方法里面返回的就是好几个检查器,下面,我们就是进入第二步了,就是实现检查器。
实现检查器
要实现的检查器就是我们在getIssues方法里返回的这几个东西.其实只需要一个就行了,我上面添加了三个是为了表明这可以有多个检查器而已。
我们创建一个WordSpellDetector类。这个类的继承关系及基本实现如下:
public class WordSpellDetector extends Detector implements Detector.JavaPsiScanner { public static final Issue ISSUE = Issue.create("WordSpellStaticFinalUpper", "Static final field should be all uppercase", "Static final field should be all uppercase as our specification", Category.CORRECTNESS, 9, Severity.WARNING, new Implementation(WordSpellDetector.class, Scope.JAVA_FILE_SCOPE)); @Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() { return super.getApplicablePsiTypes(); } @Override public JavaElementVisitor createPsiVisitor(JavaContext context) { return super.createPsiVisitor(context); }}
ISSUE显然是创建了一个提示相关的信息的封装对象。其中里面的new Implementation()返回了所要映射的检查器。
检查器里面最重要的两个方法就是getApplicablePsiTypes和createPsiVisitor。前者表示所要检查的节点类型,以前的版本里这个方法叫做getApplicableNodeTypes,现已废弃(可能是旧的那个Note接口信息太少了,现已替换成了PsiElement接口,但以前的好名字被站了咯,但起的这个新名字有点让人傻眼)。
一个完整的检查static final型的名字必须大写的检查器如下:
public class WordSpellDetector extends Detector implements Detector.JavaPsiScanner { /** * 这是一个Issue声明 */ public final static Issue ISSUE = Issue.create("WordSpellStaticFinalUpper", "Static final field should be all uppercase", "Static final field should be all uppercase as our specification", Category.CORRECTNESS, 9, Severity.WARNING, new Implementation(WordSpellDetector.class, Scope.JAVA_FILE_SCOPE)); @Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() { return Arrays.asList(PsiField.class);//声明为检查成员变量类型节点 } @Override public JavaElementVisitor createPsiVisitor(JavaContext context) { return new WordSpellChecker(context);//返回本检查器真正的检查执行者 } private class WordSpellChecker extends JavaElementVisitor { private JavaContext mContext; WordSpellChecker(JavaContext context){ this.mContext = context; } @Override public void visitField(PsiField field) {//实现哪个方法,需要跟getApplicablePsiTypes()返回的类型对应 if(field==null)return; PsiElement[] children = field.getChildren(); if(children!=null){ for (int i = 0; i < children.length; i++) { if(!isAllUpper(field.getName()) && children[i].getText().contains("final") &&children[i].getText().contains("static")){ mContext.report(ISSUE, mContext.getLocation(field), "field name with static final should be all uppercase"); } } } super.visitField(field); } } private boolean isAllUpper(String str){ if(str==null)return true; char[] chars = str.toCharArray(); for (int i = 0; i < chars.length; i++) { if(chars[i]>='a'&& chars[i]<='z')return false;//只判断有没有小写字母,不限定其它特殊字符 } return true; }}
至此,所有的编码已经完成。相关的api和要注意的地方我在代码注释里都提示了,具体还需要大家去探索它的api了。
最后要做的就是把java library构建出jar文件,放在系统用户路径下的.android/lint/子目录中,名称任意,只要是jar文件ok的。但这样会对整个studio的project都生效。如果想更好地管理这个lint文件的话,可以另外创建一个android library,把jar文件一起编译进去打出aar包或者直接依赖library到主module里即可,这些可以百度一下挺多讲到这里。
最后的效果是这样:
温馨提醒:
不管lint文件是直接放在.android目录下,还是用aar等形式,最后都需要重启android
studio才能生效。我现在用的studio 是2.3.1版本。
相关参考文章:
http://www.tuicool.com/articles/qaEZfiI
https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle
http://blog.csdn.net/u010360371/article/details/50189171
http://tools.android.com/tips/lint-custom-rules
https://github.com/yangcheng/CustomLint
https://github.com/yangcheng/LintDemoApp
http://www.tuicool.com/articles/nY3Ary
https://juejin.im/entry/576b61162e958a0078c637fb
- 自定义lint
- 自定义 Lint 规则简介
- android 自定义Lint
- android lint check的学习和自定义以及lint语法
- lint
- Lint
- lint
- 团队项目必备神器——自定义Lint
- 自动规避代码陷阱——自定义Lint规则
- 【lint】Android Lint简介
- PC LINT
- pc-lint
- lint使用
- pc-lint
- pc-lint
- Android lint
- pc-lint
- Android Lint
- java根据url获取数据
- Springboot实践
- Centos 6.9 Install Redis 3.2.9
- 文本挖掘之特征选择(python实现)
- ES2015函数扩展
- 自定义lint
- Linux jar包 后台运行
- MySQL表名、字段、查询大小写问题
- eclipse常用快捷键
- shell中几种常用截取字符串的方法
- es6笔记5^_^set、map、iterator
- 【C#】机房重构——七层登录
- 百度一面
- JSP&Servlet5(二) --- 应用程序事件 监听器