概述
Android Lint是Google提供给Android开发者的静态代码检查工具。使用Lint对Android工程代码进行扫描和检查,可以发现代码潜在的问题,提醒程序员及早修正。
为保证代码质量,美团在开发流程中加入了代码检查,如果代码检测到问题,则无法合并到正式分支中,这些检查中就包括Lint。
为什么需要自定义
我们在实际使用Lint中遇到了以下问题:
- 原生Lint无法满足我们团队特有的需求,例如:编码规范。
- 原生Lint存在一些检测缺陷或者缺少一些我们认为有必要的检测。
基于上面的考虑,我们开始调研并开发自定义Lint。
自定义Lint入门
在介绍美团的实践之前,先用一个小例子,来看看如何进行自定义Lint。
示例介绍
开发中我们希望开发者使用RoboGuice的Ln替代Log/System.out.println。
Ln相比于Log有以下优势:
- 对于正式发布包来说,debug和verbose的日志会自动不显示。
- 拥有更多的有用信息,包括应用程序名字、日志的文件和行信息、时间戳、线程等。
- 由于使用了可变参数,禁用后日志的性能比Log高。因为最冗长的日志往往都是debug或verbose日志,这可以稍微提高一些性能。
- 可以覆盖日志的写入位置和格式。
这里我们以此为例,让Lint检查代码中Log/System.out.println的调用,提醒开发者使用Ln。
创建Java工程,配置Gradle
1 2 3 4 5 6 7 |
apply plugin: 'java' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.tools.lint:lint-api:24.5.0' compile 'com.android.tools.lint:lint-checks:24.5.0' } |
注:
- lint-api: 官方给出的API,API并不是最终版,官方提醒随时有可能会更改API接口。
- lint-checks:已有的检查。
创建Detector
Detector负责扫描代码,发现问题并报告。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/** * 避免使用Log / System.out.println ,提醒使用Ln * * RoboGuice's Ln logger is similar to Log, but has the following advantages: * - Debug and verbose logging are automatically disabled for release builds. * - Your app name, file and line of the log message, time stamp, thread, and other useful information is automatically logged for you. (Some of this information is disabled for release builds to improve performance). * - Performance of disabled logging is faster than Log due to the use of the varargs. Since your most expensive logging will often be debug or verbose logging, this can lead to a minor performance win. * - You can override where the logs are written to and the format of the logging. * * https://github.com/roboguice/roboguice/wiki/Logging-via-Ln * * Created by chentong on 18/9/15. */ public class LogDetector extends Detector implements Detector.JavaScanner{ public static final Issue ISSUE = Issue.create( "LogUse", "避免使用Log/System.out.println", "使用Ln,防止在正式包打印log", Category.SECURITY, 5, Severity.ERROR, new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)); @Override public List> getApplicableNodeTypes() { return Collections.>singletonList(MethodInvocation.class); } @Override public AstVisitor createJavaVisitor(final JavaContext context) { return new ForwardingAstVisitor() { @Override public boolean visitMethodInvocation(MethodInvocation node) { if (node.toString().startsWith("System.out.println")) { context.report(ISSUE, node, context.getLocation(node), "请使用Ln,避免使用System.out.println"); return true; } JavaParser.ResolvedNode resolve = context.resolve(node); if (resolve instanceof JavaParser.ResolvedMethod) { JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolve; // 方法所在的类校验 JavaParser.ResolvedClass containingClass = method.getContainingClass(); if (containingClass.matches("android.util.Log")) { context.report(ISSUE, node, context.getLocation(node), "请使用Ln,避免使用Log"); return true; } } return super.visitMethodInvocation(node); } }; } } |
可以看到这个Detector继承Detector类,然后实现Scanner接口。
自定义Detector可以实现一个或多个Scanner接口,选择实现哪种接口取决于你想要的扫描范围
- Detector.XmlScanner
- Detector.JavaScanner
- Detector.ClassScanner
- Detector.BinaryResourceScanner
- Detector.ResourceFolderScanner
- Detector.GradleScanner
- Detector.OtherFileScanner
这里因为我们是要针对Java代码扫描,所以选择使用JavaScanner
。
代码中getApplicableNodeTypes
方法决定了什么样的类型能够被检测到。这里我们想看Log以及println的方法调用,选取MethodInvocation
。对应的,我们在createJavaVisitor
创建一个ForwardingAstVisitor
通过visitMethodInvocation
方法来接收被检测到的Node。
可以看到get