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下载


0 0
原创粉丝点击