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

来源:互联网 发布:2017阅读软件排行 编辑:程序博客网 时间:2024/06/18 12:36

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


一、Lint是什么?


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

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

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


二、Lint的使用

在Android Studio中选择Analyze -> Inspect Code


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


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


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


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

由于项目的架构,有时候项目中会有一些非正式的代码规则,比如不使用系统自带的日志工具Log而使用第三方或二次封装过的工具类。这种情况默认的lint就无法检查了,这时候自定义lint规则就派上用场了。
自定义lint规则可以帮助团队规避一些因架构、业务、历史等原因出现的代码陷阱,避免一些问题频繁重复的产生,同时可以让团队新成员快速被动的了解一些开发规则。
下面开始一步步介绍如何自定义lint规则。


四、新建module

想要使用自定义lint规则,一种做法是将定义规则的代码打成jar包,然后放在“%UserHome%/.android/lint/”目录下。
这种做法有两个缺点:一是对所有的项目都产生作用,无法实现不同项目使用不同规则;二是需要每个人都下载并拷贝到目录下。
另外一种做法将定义的规则打包成aar的形式,依赖到项目中。
这种做法需要创建两个module,一个java-lib,一个android-lib,如下图:

在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代码,如下:
@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    Log.e("test", "use log.e");    init();}
接下来就可以测试自定义的规则是否生效了。


九、执行lint

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


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


可以看到,我们定义的规则已经使用了,找到了一处使用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:问题的实现,Implementation类型

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

(2)然后通过重写必要的函数来实现一定的查找规则,代码如下:
@Overridepublic List<String> getApplicableCallNames() {    return Arrays.asList("v", "d", "i", "w", "e", "wtf");}@Overridepublic 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函数实现问题逻辑,代码如下:
@Overridepublic 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

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝不想退款了怎么办 淘宝卖家不退款怎么办投诉 淘宝买家恶意投诉怎么办 买家要求延迟发货怎么办 淘宝恶意下单怎么办 买家给中评不接电话怎么办 买家虚假发货了怎么办 闲鱼买家不取货怎么办 退货填错单号怎么办 买家不承认收货怎么办 买家收货已超时怎么办 淘宝客人拒收货怎么办 买家淘宝申诉失败怎么办 淘宝改不了地址怎么办 退货订单号错了怎么办 淘宝客服不理我怎么办 信件没贴邮票怎么办 不想穿的衣服怎么办 相机拍不了照片怎么办 资金融资被骗了怎么办 微信号找不到了怎么办 拼多多访客下降怎么办? 优化过度被降权了怎么办 电影胶片断了怎么办 休闲裤皱了怎么办 裤子太容易邹怎么办 裤子穿上有褶皱怎么办 前端学不会js怎么办 漆皮鞋太亮了反光怎么办 运动裤买长了怎么办 西服裤子太长了怎么办 苹果主板被偷怎么办 乐视手机漏电怎么办 店铺承租方得不到补偿怎么办? 淘宝空间满了怎么办 微销通登录不上怎么办 淘宝宝贝被监控怎么办 邮政快递不派送怎么办 快递员不接电话怎么办 小米电视不发货怎么办 门禁卡没有磁性怎么办