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 getApplicableCallNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public List 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 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:问题的实现,Implementation类型
I
mplementation类的
构造函数中第一个参数是定义问题的类;第二个参数是文件范围,即在什么类型的文件中扫描这个问题。
(2)然后通过重写必要的函数来实现一定的查找规则,代码如下:
@Override
public List getApplicableCallNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public List 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