内容简介:自动规避代码陷阱——自定义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
然后在弹出窗中选择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代码,如下:
@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,双击执行即可,如下
执行时就可以中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:问题的实现, 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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 内网流量规避
- 如何规避Sysmon DNS监控
- 混淆加密流量规避检测:黑客利用加密流量趋势明显
- 99%的人会中招的运维安全陋习,请规避!
- 金融机构如何规避DevOps安全风险?权威报告给了几组数据
- 警惕 Go 编程陷阱
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。