android lint check的学习和自定义以及lint语法
来源:互联网 发布:用java求最小公倍数 编辑:程序博客网 时间:2024/05/18 20:07
lint介绍
android lint是一个静态代码分析工具,通过lint工具,你可以不用边运行边调试,或者通过单元测试进行代码检查,可以检测代码中不规范、不和要求的问题,解决一些潜在的bug。lint工具可以在命令行上使用,也可以在adt中使用。
比如当想检查在manifest.xml中是否有activity,activity中是否包含了launcher activity。如果没有进行错误的警告。
通过lint的这种手段,可以对代码进行规范的控制,毕竟一个团队每个人的风格不同,但是要注意的当然是代码的质量,所以lint可以进行代码的规范和质量控制。
在Android studio还没出来时,lint和Eclipse并不能很好的结合在一起,只能作为一个独立的工具,通过命令行去执行lint检查。
在android studio出现之后,不再建议单独使用lint命令,而是结合gradle进行操作,命令为* ./gradlew lint *进行执行
lint工具通过一下六个方面去检查代码中的问题correctness, security, performance, usability, accessibility, and internationalization。检查的范围包括java文件,xml文件,class文件。
lint工具在sdk16版本之后就带有了,所以在sdk目录/tools/可以找到lint工具。现在建议与gradle一起使用,使用./gradlew lint进行
参考官方文档介绍
使用lint的方法
关于lint的一些命令,可以参考官网,这里简单介绍一些。
- lint path(项目目录) ——进行项目的lint检查
- lint –disable id(MissingTranslation,UnusedIds,Usability:Icons) path ——id是lint issue(问题)的标志,检查项目,不包括指定的issue
- lint –check id path ——利用指定的issue进行项目检查
- lint –list ——列出所有的issue
- lint –show id ——介绍指定的issue
- lint –help ——查看帮助
1.使用android studio自带的lint工具
点击Analyze的Inspect Code选项,即可开启lint检查,在Inspection窗口中可以看到lint检查的结果,lint查询的错误类型包括:
- Missing Translation and Unused Translation【缺少翻译或者没有】
- Layout Peformance problems (all the issues the old layoutopt tool used to find, and more)【布局展示问题】
- Unused resources【没有使用的资源】
- Inconsistent array sizes (when arrays are defined in multiple configurations)【不一致的数组大小】
- Accessibility and internationalization problems (hardcoded strings, missing contentDescription, etc)【可访问性和国际化问题,包括硬链接的字符串,缺少contentDescription,等等】
- Icon problems (like missing densities, duplicate icons, wrong sizes, etc)【图片问题,丢失密度,重复图片,错误尺寸等】
- Usability problems (like not specifying an input type on a text field)【使用规范,比如没有在一个文本上指定输入的类型】
- Manifest errors【Manifest.xml中的错误】
- and so on
android自带的lint规则的更改可以在Setting的Edit选项下选择Inspections(File > Settings > Project Settings),对已有的lint规则进行自定义选择。
参考官方文档
2.使用lint.xml定义检查规则
可以通过lint.xml来自定义检查规则,这里的自定义是指定义系统原有的操作,所以和第一个步骤的结果是一样的,只是可以更方便的配置。
lint.xml生效的位置是要放在项目的根目录下面,lint.xml的示例如下:
<?xml version="1.0" encoding="UTF-8"?><lint> <!-- 忽略指定的检查 --> <issue id="IconMissingDensityFolder" severity="ignore" /> <!-- 忽略指定文件的指定检查 --> <issue id="ObsoleteLayoutParam"> <ignore path="res/layout/activation.xml" /> <ignore path="res/layout-xlarge/activation.xml" /> </issue> <!-- 更改检查问题归属的严重性 --> <issue id="HardcodedText" severity="error" /></lint>
参考官方文档
3.自定义lint检查规则
使用google提供的lint规则,确实能找出项目代码中不规范的地方,同时也帮助纠正了许多编译时错误。除了使用google提供的lint规则,我们当然也希望我们能够通过自定义lint规则来为自己的项目进行一个代码检测,包括代码质量把控,查找关键代码位置,或者检测代码规范,比如是否在用了流的地方关闭了流。那么如何自定义lint规则和如何将写好的规则加入已有的lint规则中,就是接下来呀研究的地方。
如何加入已有的lint规则
lint规则是基于JAVA写的,是在AST抽象语法树上去进行一个解析。所以在写lint规则的时候,要学习一下AST抽象语法树。才知道如何去寻找一个类方法和其参数等。以下有两种方法:
(1)所以自定义lint规则应该是一个写好的jar包,jar包生效的位置是在~/.android/lint目录,这个是对于Mac和Linux来说的,对于Windows来说就是在C:/Users/Administrator/.android/lint下,放到这个目录下,lint工具会自动加载这个jar包作为lint的自定义检查规则。
(2)放到lint目录下着实是一件比较麻烦的事情,即使可以用脚本来代替,但是仍然不是一个特别方便的方法。也是由于当android项目直接依赖于lint.jar包时不能起作用,而无法进行直接依赖。
而aar很好的解决了这个问题,aar能够将项目中的资源、class文件、jar文件等都包含,所以通过将lint.jar放入lintaar中,再由项目依赖于lintaar,这时候就可以达到自定义lint检查的目的。
下面就是如何使自定义lint生效的代码示例,使用第二个方法(第二个方法就包括了第一个方法):
主要包括了两个Module一个是lintJar,一个是lintAar。还有一个是测试的app项目。使用自定义lint的主要方式是利用lintAar将lintJar生成的jar包大包成aar,方便引用。所以主要是gradle的操作。
在lintJar中,主要是编写lint规则。它的gradle如下:
apply plugin: 'java'// 依赖于lint的规则的apidependencies { compile 'com.android.tools.lint:lint-api:24.3.1' compile 'com.android.tools.lint:lint-checks:24.3.1' testCompile 'com.android.tools.lint:lint-tests:24.3.1'}/** * Lint-Registry是透露给lint工具的注册类的方法,也就是PermissionIssueRegistry是lint工具的入口,同时也通过这个方法进行打jar包 */jar { manifest { attributes("Lint-Registry": "com.mogujie.PermissionIssueRegistry") }}defaultTasks 'assemble'// 定义一个方法lintJarOutputconfigurations { lintJarOutput}// 指定定义方法lintJarOutput的作用,此处是获得调用jar方法后的生成的jar包dependencies { lintJarOutput files(jar)}
在lintAar中,gradle如下:
apply plugin: 'com.android.library'android { // 此处省略,太多了,就是普通的as生成的模板}// 依赖所有jar结尾的jar包dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])}// 定义方法lintJarImportconfigurations { lintJarImport}// 链接到lintJar中的lintJarOutput方法,调用jar方法,并获得jar包dependencies { lintJarImport project(path: ':lintjar', configuration: 'lintJarOutput')}// 将得到的JAR包复制到目录build/intermediates/lint/下task copyLintJar(type: Copy) { from (configurations.lintJarImport) { rename { String fileName -> 'lint.jar' } } into 'build/intermediates/lint/'}// 当项目build到compileLint这一步时执行copyLintJar方法project.afterEvaluate { def compileLintTask = project.tasks.find{ it.name == 'compileLint'} compileLintTask.dependsOn(copyLintJar)}
在app项目中,为了方便在命令窗口看到输出的信息,还需要在gradle中添加一段代码:
android { lintOptions { textReport true // 输出lint报告 textOutput 'stdout' abortOnError false // 遇到错误不停止 }}
参考官方文章
参考的文章
如何编写lint规则
编写lint规则实际上是利用了java的语法树进行一个结构的遍历,是利用java语言进行开发的,使用的Google提供的lint检查api。
先设定一个问题:检查流的close方法是否在finally中被调用。
对于这个问题的想法是
1.先检查流的close方法所在的位置。
2.检查close外层的try/catch所在位置
3.检查try/catch外层的try/catch,并与finally的行数进行判断
lint规则时在libjar项目中进行编写,所以gradle需要依赖lint的相关api。
首先先看下需要定义的注册类,也就是暴露给lint的入口,这个入口需要在gradle中进行注册,请看前面的哦。IssueRegistry就是注册类,继承他,并重写getIssues的方法即可。
MyIssueRegistry.java
/** * 把所定义的Issue进行导出,用于提供此jar中所有输出的lint规则,这个类是暴露给lint的一个注册类 */public class MyIssueRegistry extends IssueRegistry { @Override public List<Issue> getIssues() { System.out.println("***************************************************"); System.out.println("**************** lint is starting *****************"); System.out.println("***************************************************"); return Arrays.asList( CloseDetector.ISSUE ); }}
Detector就是检查的工具类,通过继承Detector实现自己的检查规则
Issue就是定义的问题的描述,通过Issue可将检查结果进行反馈
Detector.JavaScanner是针对于java文件扫描的接口文件,以及一些方法。除此之外海游XmlScanner, GradleScanner, ClassScanner, BinaryResourceScanner, ResourceFolderScanner, OtherFileScanner。
由于刚刚提出的问题是关于close的,则Detector定义为CloseDetector。
CloseDetector.java
/** * 定义代码检查规则 * 这个是针对try和catch中的finally是否包涵close方法进行一个判断 * 由于要对java代码进行扫描,因此继承的是javascanner的接口 */public class CloseDetector extends Detector implements Detector.JavaScanner { public static Issue ISSUE = Issue.create( "CloseMethod", "close方法应该在finally中调用", "close方法应该在finally中调用,防止导致内存泄漏", // 这个主要是用于对问题的分类,不同的问题就可以集中在一起显示。 Category.CORRECTNESS, // 优先级 6, // 定义查找问题的严重级别 Severity.ERROR, // 提供处理该问题的Detector和该Detector所关心的资源范围。当系统生成了抽象语法树(Abstract syntax tree,简称AST),或者遍历xml资源时,就会调用对应Issue的处理器Detector。 new Implementation(CloseDetector.class, Scope.JAVA_FILE_SCOPE) ); // 限定关心的方法的调用类 public static final String[] sSupportSuperType = new String[]{ "java.io.InputStream", "java.io.OutputStream", "android.database.Cursor" }; /** * 只关心名是close的方法 * * @return */ @Override public List<String> getApplicableMethodNames() { return Collections.singletonList("close"); } /** * 该方法调用时,会传入代表close方法被调用的节点MethodInvocation,以及所在java文件的上下文JavaContext, * 还有AstVisitor。由于我们没有重写createJavaVisitor方法,所以不用管AstVisitor。 * MethodInvocation封装了close被调用处的代码,而结合JavaContext对象,即可寻找对应的上下文,来帮助我们判断条件。 * * @param context * @param visitor * @param node */ @Override public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) { // 判断类型,看下所监测的资源是否是我们定义的相关资源 // 通过JavaContext的resolve的方法,传入node节点,由于所有的AST树上的节点都继承自NODE,所以可以通过node去找到class JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) context.resolve(node); JavaParser.ResolvedClass clzz = method.getContainingClass(); boolean isSubClass = false; for (int i = 0; i < sSupportSuperType.length; i++) { if (clzz.isSubclassOf(sSupportSuperType[i], false)) { isSubClass = true; break; } } if (!isSubClass) super.visitMethod(context, visitor, node); /** * 查找try和block的信息 * 在AST中,close 代码节点应该是try的一个子孙节点(try是语法上的block),所以从close代码节点向上追溯, * 可以找到对应的try,而Node对象本来就有getParent方法,所以可以递归调用该方法来找到Try节点(这也是一个节点), * 或者调用JavaContext的查找限定parent类型的方法: */ Try fTryBlock = context.getParentOfType(node, Try.class); int fLineNum = context.getLocation(fTryBlock).getStart().getLine(); System.out.println(" fLineNum=" + fLineNum); /** * 如果close在try模块中,接着就要对try进行向上查找,看try是否被包裹在try中,同时,是否处于finally中,try节点 * 有一个astFinally的方法,可以得到finally的节点,只要判断节点的位置,既可以实现判断close是否在finally中 */ Try sTryBlock = context.getParentOfType(fTryBlock, Try.class); Block finaBlock = sTryBlock.astFinally(); int sLineNum = context.getLocation(finaBlock).getStart().getLine(); System.out.println(" sLineNum=" + sLineNum); /** * 若我们确定了close是在try 块中,且try块不在finally里,那么就需要触发Issue,这样在html报 * 告中就可以找到对应的信息了 * 一个莫名的bug,不能再这里写成 * if (fLineNum < sLineNum){ * context.report(ISSUE, node, context.getLocation(node), "请在finally中调用close"); * } * 否则再直接运行Analyze下Inspect选项后,在inspection窗口中没有办法看到Error from custom lint Check * 的错误信息 */ if (fLineNum > sLineNum) { return; } else { context.report(ISSUE, node, context.getLocation(node), "please use close method in finally"); } }}
通过短短的示例学习一下lint规则的编写。由于网上关于lint规则的编写还是比较少的,所以要想了解lint规则,还需要多研究下google的源代码,从源代码中进行学习。
官方源码
注意
在使用android studio中的lint检查的工具的时候,有时候需要退出,然后重新启动之后,我门加入的lint.aar才会在系统中生效。
同时也可以在命令窗口上实时调试,请看文中所述。
项目demo下载
- android lint check的学习和自定义以及lint语法
- 【lint】Android Lint工具学习
- android 自定义Lint
- Android Lint工具学习
- Android Lint工具学习
- 【lint】Android Lint简介
- Android-Lint的简述
- Android-Lint的使用
- Android lint的使用
- Android Lint的使用
- android lint的使用
- Android Lint的使用
- Android Lint 的使用
- 自定义lint
- Lint API Check
- Android lint
- Android Lint
- android lint
- 模拟单链表及其基本操作
- [UVA 10459]The Tree Root[树上最长距离]
- Struts2 中#、@、%和$符号的用途
- 10015---jQuery--jQuery DOM 操作-删除元素
- C++继承中的名字查找
- android lint check的学习和自定义以及lint语法
- 电子或通信领域当前的主流技术及其社会需求调查报告
- Linux常用命令(一)
- 【数据结构】跳表
- php curl 入门
- 如何高效利用时间学习?
- android 学习之触摸事件 -- 认识MotionEvent
- 通向企业级的 OPENSTACK 网络服务
- C 数组指针和指针数组的区别