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

栏目: Java · 发布时间: 6年前

内容简介:自动规避代码陷阱——自定义Lint规则

Android进阶之路系列: http://blog.csdn.net/column/details/16488.html

源码: https://github.com/chzphoenix/LintRulesForAndroid

一、Lint是什么?

Lint 是一款静态代码分析工具,能检查安卓项目的源文件,从而查找潜在的程序错误以及优化提升的方案。

当你忘记在Toast上调用show()时,Lint 就会提醒你。它也会确保你的ImageView中添加了contentDescription,以支持可用性。类似的例子还有成千上万个。诚然,Lint 能在诸多方面提供帮助,包括:正确性,安全,性能,易用性,可用性,国际化等等。

这是引用网上的一段描述,简单来说lint可以对代码进行检查分析,查找各类潜在问题。

二、Lint的使用

在Android Studio中选择Analyze -> Inspect Code

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

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

然后在弹出窗中选择Whole project,点击确定开始检查

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

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

检查结束就可以在下面看到结果了,如下图:

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

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

关于lint的使用不是本文的重点,这里只是简单介绍一下。

三、为什么要使用自定义Lint规则?

由于项目的架构,有时候项目中会有一些非正式的代码规则,比如不使用系统自带的日志工具Log而使用第三方或二次封装过的 工具 类。这种情况默认的lint就无法检查了,这时候自定义lint规则就派上用场了。

自定义lint规则可以帮助团队规避一些因架构、业务、历史等原因出现的代码陷阱,避免一些问题频繁重复的产生,同时可以让团队新成员快速被动的了解一些开发规则。

下面开始一步步介绍如何自定义lint规则。

四、新建module

想要使用自定义lint规则,一种做法是将定义规则的代码打成jar包,然后放在“%UserHome%/.android/lint/”目录下。

这种做法有两个缺点:一是对所有的项目都产生作用,无法实现不同项目使用不同规则;二是需要每个人都下载并拷贝到目录下。

另外一种做法将定义的规则打包成aar的形式,依赖到项目中。

这种做法需要创建两个module,一个java-lib,一个android-lib,如下图:

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

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

在lintjar中编写规则代码,而lintaar没有任何代码,它的作用是将lintjar的jar包打包成aar以便引用。

五、在lintjar中定义规则

在lintjar中新建一个类,继承Detector,实现一个规则,如下:

public class LogDetector extends Detector implements Detector.ClassScanner {
    public static final Issue ISSUE = Issue.create("LogUtilsNotUsed",
            "You must use our `LogUtils`",
            "Logging should be avoided in production for security and performance reasons. Therefore, we created a LogUtils that wraps all our calls to Logger and disable them for release flavor.",
            Category.MESSAGES,
            9,
            Severity.ERROR,
            new Implementation(LogDetector.class,
                    Scope.CLASS_FILE_SCOPE));

    @Override
    public List<String> getApplicableCallNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override
    public void checkCall(@NonNull ClassContext context,
                          @NonNull ClassNode classNode,
                          @NonNull MethodNode method,
                          @NonNull MethodInsnNode call) {
        String owner = call.owner;
        if (owner.startsWith("android/util/Log")) {
            context.report(ISSUE,
                    method,
                    call,
                    context.getLocation(call),
                    "You must use our `LogUtils`");
        }
    }
}

LogDetector的作用是检查代码中是否使用Log类,建议使用封装过的"LogUtils"类。

其中代码的意义和功能我们稍后再细说,目前只需要知道继承 Detector来实现一个规则就可以了。

然后我们还需要另外一个类,继承IssueRegistry,这个类的作用是将定义规则注册上,代码很简单,如下:

public class LintRegistry extends IssueRegistry {
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(InitCallDetector.ISSUE, LogDetector.ISSUE);
    }
}

这样还没有完成注册,要完成注册我们还需要在gradle中进行配置。 

六、配置lintjar中gradle

在lintjar的gradle中引入lint的两个库

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.tools.lint:lint-api:24.3.1'
    compile 'com.android.tools.lint:lint-checks:24.3.1'
}

然后,注册我们之前定义好的Registry类

/**

* Lint-Registry是lint的注册类

*/

jar {

manifest {

attributes( "Lint-Registry""com.bennu.lintjar.LintRegistry" )

}

}

最后定义一个打包方法,这个会在lintaar中使用

//定义lintJarOutput方法,在lintaar中被调用
configurations {
    lintJarOutput
}

dependencies {
    //lintJarOutput方法,打jar包
    lintJarOutput files(jar)
}

这样lintJar这个module就可以了,下面开始配置lintAar这个module。

七、配置LintAar的gradle

LintAar这个module中不需要写任何代码,它的作用是将lintJar生成的jar包再打包成aar即可。

在LintAar的gradle中添加如下:

// 定义lintJarImport方法,在copyLintJar任务中被调用
configurations {
    lintJarImport
}

dependencies {
    // 调用lintjar的lintJarOutput方法,获得jar包
    lintJarImport project(path: ':lintjar', configuration: 'lintJarOutput')
}

// 调用lintJarImport得到jar包,拷贝到指定目录
task copyLintJar(type: Copy) {
    from (configurations.lintJarImport) {
        rename {
            String fileName ->
                'lint.jar'
        }
    }
    into 'build/intermediates/lint/'
}

// 当项目执行到prepareLintJar这一步时执行copyLintJar方法(注意:这个时机需要根据项目具体情况改变)
project.afterEvaluate {
    def compileLintTask = project.tasks.find{ it.name == 'prepareLintJar'}
    compileLintTask.dependsOn(copyLintJar)
}

定义一个lintJarImport方法,这个方法会调用lintJar中的lintJarOutput方法得到jar包。

新建一个copyLintJar的任务task,目的是将前面得到的jar包拷贝到指定的目录。

最后在afterEvaluate中判断当执行了‘prepareLintJar’这个task时执行copyLintJar这个任务。

注意: ‘prepareLintJar’是基于我自己的环境判断出来的,在不同的gradle版本上可能有所不同,请根据实际情况修改copyLintJar的执行时机。

这样lintjar和lintaar这两个module都完成了,下一步将它们依赖进项目。

八、在项目中引入规则

在项目的gradle中引入lintaar

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation project(':lintaar')
}

同时添加如下配置

android {
    
    ...
    
    lintOptions {
        textReport true // 输出lint报告
        textOutput 'stdout'
        abortOnError false // 遇到错误不停止
    }
}

然后,我们在代码中随便写个Log代码,如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.e("test", "use log.e");
    init();
}

接下来就可以测试自定义的规则是否生效了。

九、执行lint

点开gradle窗口,在项目(如:app)下找到lint的相关task,双击执行即可,如下

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

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

执行时就可以中Message窗口中看到相关信息,如下

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

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

可以看到,我们定义的规则已经使用了,找到了一处使用Log类的代码。

十、如何定义规则

上面我们实现了规则并成功使用了,但是对于规则的定义,即Detector类一笔带过,这个其实才是重点,下面我们以LogDetector为例详细说说如何定义自己的规则。

(1)首先创建一个Issue对象,如下:

public static final Issue ISSUE = Issue.create("LogUtilsNotUsed",
        "You must use our `LogUtils`",
        "Logging should be avoided in production for security and performance reasons. Therefore, we created a LogUtils that wraps all our calls to Logger and disable them for release flavor.",
        Category.MESSAGES,
        9,
        Severity.ERROR,
        new Implementation(LogDetector.class,
                Scope.CLASS_FILE_SCOPE));

Issue的create函数有七个参数:

  • id:问题的id
  • briefDescription:问题的简单描述
  • explanation:问题的解释,即如何解决问题
  • category:问题的类型,具体间Category类
  • priority:问题的重要程度,从1到10,10是最重要
  • severity:问题的严重性,有ERROR、WARNING等
  • implementation:问题的实现, I mplementation类型

I mplementation类的 构造函数中第一个参数是定义问题的类;第二个参数是文件范围,即在什么类型的文件中扫描这个问题。

(2)然后通过重写必要的函数来实现一定的查找规则,代码如下:

@Override
public List<String> getApplicableCallNames() {
    return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}

@Override
public List<String> getApplicableMethodNames() {
    return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}

重写了 Detector 的两个函数,来查找调用的方法名是“v”、“d”等的那部分代码。

Detector有很多Scanner,每个Scanner又有不少函数,这里就不一个个来说了。具体需要使用那个Scanner的哪些函数,需要大家根据自己的情况,结合Detector源码中每个函数的说明来自己判断。这部分网上的资料不多,后续的文章中,我可能会就几个例子讲解一些函数的使用。

在上面的代码中用来两个函数getApplicableCallNames和 getApplicableMethodNames。其中 getApplicableCallNames是ClassScanner的函数,而 getApplicableMethodNames是JavaScanner的函数,两个函数作用是一样的,这两个函数会返回一个字符串列表,检查时当发现方法调用而且调用的方法名在列表中时,就会触发check。

(3)最后重写check函数实现问题逻辑,代码如下:

@Override
public void checkCall(@NonNull ClassContext context,
                      @NonNull ClassNode classNode,
                      @NonNull MethodNode method,
                      @NonNull MethodInsnNode call) {
    String owner = call.owner;
    if (owner.startsWith("android/util/Log")) {
        context.report(ISSUE,
                method,
                call,
                context.getLocation(call),
                "You must use our `LogUtils`");
    }
}

检查每次函数(已过滤)调用,当调用主体是Log类时,使用ClassContext的report函数上报一个问题。

report函数有5个参数:

  • issue:上面定义的ISSUE
  • method:MethodNode类型
  • instruction:MethodInsnNode类型
  • location:问题的位置
  • message:问题描述

通过上面这个简单的事例,一个问题规则的定义基本上就是通过上面三步来完成。

十一、总结

本篇文章主要是讲解一下如何在项目中完成一个自定义lint的引入,并且通过一个简单的例子讲解如何创建一个简单的规则,并且运行查看结果。

源码: https://github.com/chzphoenix/LintRulesForAndroid

Android进阶之路系列: http://blog.csdn.net/column/details/16488.html


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

有限与无限的游戏

有限与无限的游戏

[美]詹姆斯·卡斯 / 马小悟、余倩 / 电子工业出版社 / 2013-10 / 35.00元

在这本书中,詹姆斯·卡斯向我们展示了世界上两种类型的「游戏」:「有限的游戏」和「无限的游戏」。 有限的游戏,其目的在于赢得胜利;无限的游戏,却旨在让游戏永远进行下去。有限的游戏在边界内玩,无限的游戏玩的就是边界。有限的游戏具有一个确定的开始和结束,拥有特定的赢家,规则的存在就是为了保证游戏会结束。无限的游戏既没有确定的开始和结束,也没有赢家,它的目的在于将更多的人带入到游戏本身中来,从而延续......一起来看看 《有限与无限的游戏》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具