自定义 Lint 规则简介

时间:2023-02-17 00:28:38

上个月,笔者在巴黎 Droidcon 的 BarCamp 研讨会上聆听了 Matthew Compton 关于编写自己的 Lint 规则的讲话。深受启发之后,笔者想就此话题做进一步的探索。

定义

如果你是安卓开发者,那你一定已经知道 Lint 的定义。

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

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

Lint 易于使用,通过简单的 Gradle 任务:./gradlew lint 就能在任意安卓项目上运行。它会生成一份报告,指出它的发现并按照种类、优先级和严重程度对问题进行分类。这份报告能确保代码质量,防止 app 中出现代码错误,因此应该时刻进行监控。

在简单的介绍之后,笔者希望大家能达成共识:Lint 是理解一些安卓 API 框架使用情况的好帮手。

为什么要自己写 Lint 规则?

大多数开发者可能都不知道:你可以自己写 Lint 规则。其实,在很多使用案例中,自定义的 Lint 规则往往大有用处:

  1. 如果你在写一个代码库/SDK,你想帮助开发者正确地使用它,Lint 规则就能派上用场。有了 Lint,你可以轻易地提醒他们忽略或做错的事情。

  2. 如果你的团队有了新加入的开发者,Lint 可以帮助他快速了解团队的最佳实践,或命名惯例。

一些例子

你可能知道,笔者最近加入了 CaptainTrain 安卓团队。下面的例子基于笔者为自己的 app 创建的两条 Lint 规则,这些规则完美地展示了 Lint 确保开发者遵循项目编码实践的妙用。

Gradle

自定义的 Lint 规则必须实现在一个新的模块中。以下是一个 build.gradle 例子:

apply plugin: 'java'

targetCompatibility = JavaVersion.VERSION_1_7
sourceCompatibility = JavaVersion.VERSION_1_7 configurations {
lintChecks
} dependencies {
compile 'com.android.tools.lint:lint-api:24.3.1'
compile 'com.android.tools.lint:lint-checks:24.3.1' lintChecks files(jar)
} jar {
manifest {
attributes('Lint-Registry': 'com.captaintrain.android.lint.CaptainRegistry')
}
} defaultTasks 'assemble' task install(type: Copy, dependsOn: build) {
from configurations.lintChecks
into System.getProperty('user.home') + '/.android/lint/'
}

如你所见,为了实现自定义 Lint 规则,需要两个编译依赖关系。此外,还需要确切的 Lint-Registry,后文会介绍这是什么,现在只需记住这是强制要求。最后,创建一个小任务来快速安装新的 Lint 规则。

接着,使用../gradlew clean install编译并部署该模块。

配置好模块之后,让我们来看看如何编写第一条规则。

规则一:Attr (属性)必须有前缀

在 CaptainTrain 项目中,我们都会在属性前面添加ct前缀,从而避免与其他代码库发生冲突。新的开发者很容易忘记这一点,因此笔者写了如下规则:

public class AttrPrefixDetector extends ResourceXmlDetector {

 public static final Issue ISSUE = Issue.create("AttrNotPrefixed",
"You must prefix your custom attr by `ct`",
"We prefix all our attrs to avoid *es.",
Category.TYPOGRAPHY,
5,
Severity.WARNING,
new Implementation(AttrPrefixDetector.class,
Scope.RESOURCE_FILE_SCOPE)); // Only XML files
@Override
public boolean appliesTo(@NonNull Context context,
@NonNull File file) {
return LintUtils.isXmlFile(file);
} // Only values folder
@Override
public boolean appliesTo(ResourceFolderType folderType) {
return ResourceFolderType.VALUES == folderType;
} // Only attr tag
@Override
public Collection<String> getApplicableElements() {
return Collections.singletonList(TAG_ATTR);
} // Only name attribute
@Override
public Collection<String> getApplicableAttributes() {
return Collections.singletonList(ATTR_NAME);
} @Override
public void visitElement(XmlContext context, Element element) {
final Attr attributeNode = element.getAttributeNode(ATTR_NAME);
if (attributeNode != null) {
final String val = attributeNode.getValue();
if (!val.startsWith("android:") && !val.startsWith("ct")) {
context.report(ISSUE,
attributeNode,
context.getLocation(attributeNode),
"You must prefix your custom attr by `ct`");
}
}
}
}

如你所见,我们继承了ResourceXmlDetector类。Detector 类允许我们发现问题,并报告Issue。首先,我们必须明确寻找什么:

  • 第一个appliesTo方法会只保留 XML 文件。
  • 第二个appliesTo方法会只保留资源文件夹中的values
  • getApplicableElements 方法会只保留attr XML 元素。
  • getApplicableAttributes 方法会只保留name XML 属性。

过滤之后,我们使用简单的算法实现visitElement方法。一旦发现某个attr XML 标记的name属性不源自安卓也不以ct前缀,我们就报告一个Issue。该Issue按照如下方式声明在类的头部:

public static final Issue ISSUE = Issue.create("AttrNotPrefixed",
"You must prefix your custom attr by `ct`",
"To avoid *es, we prefixed all our attrs.",
Category.TYPOGRAPHY,
5,
Severity.WARNING,
new Implementation(AttrPrefixDetector.class,
Scope.RESOURCE_FILE_SCOPE));

其中,每个参数都很重要,而且是强制性参数。

  • AttrNotPrefixed 是 Lint 规则的 id,必须是唯一的。
  • You must prefix your custom attr by ct(必须以 ct 作为自定义属性的前缀)是简述。
  • To avoid *es, we prefixed all our attrs.(为避免冲突,所有属性均添加前缀。)是更为详细的解释。
  • 5是优先级系数。必须是1到10之间的某个值。
  • WARNING 是严重程度。此处我们只选择WARNING,这样即便存在该问题,代码也能安全运行。
  • ImplementationDetector间的桥梁,用于发现问题。Scope则用于分析问题。在本例中,我们必须处于资源文件层面才能分析前缀问题。

你可能也发现了,其实所需的代码非常简单易懂。你只需小心所用的范围以及为Issue输入的值即可。

Lint 报告可能得出的结果如下:

自定义 Lint 规则简介

规则二:生产环境下禁止 log

在 CaptainTrain 应用中,我们将所有Log调用都包装到一个新的类里。由于在生产环境下,日志有可能妨碍应用性能与用户数据的安全,该类旨在BuildConfig.DEBUG为非时禁用日志。此外,该类还能帮助日志排版,以及提供一些其他特性。举例如下:

public class LoggerUsageDetector 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(LoggerUsageDetector.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`");
}
}
}

如你所见,规则二的模式与规则一相同。方法getApplicableCallNamesgetApplicableMethodNames用于明确寻找的目标。之后,我们找出问题并创建之。唯一的不同在于,我们不再继承XmlResourceDetector类,而是仅继承Detector类,并实现ClassScanner接口以处理 Java 类检查。所以,实际上,规则二的变化没有很多。如果仔细查看XmlResourceDetector类,会发现它只是实现XmlScannerDetector类。因此,所有规则都适用的总结如下:我们只需继承Detector并实现合适的Scanner接口即可。

最后,改变Issue的范围并关闭CLASS_FILE_SCOPE。此处,要想找到问题,只需分析一个 Java 类文件即可。有时,你需要分析多个 Java 类文件才能发现问题,所以你需要使用ALL_CLASS_FILES。范围的选择非常重要,因此请小心谨慎。点击此处可查看全部范围。

虽然问题描述可能不很清楚,但一个Detector可以发现多个问题。此外,通过一次运行就能处理所有问题,因此可以有效提高应用性能。

规则二的 Lint 报告结果举例如下:

自定义 Lint 规则简介

登记

此处,我们遗漏了一项重要的事情:登记!我们需要将新创建的问题登记到所有处理过的 lint 检查列表中:

public final class CaptainRegistry extends IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(LoggerUsageDetector.ISSUE, AttrPrefixDetector.ISSUE);
}
}

如你所见,登记过程也非常简单。我们只需继承IssueRegistry类并实现getIssues方法,从而返回我们的自定义问题。该类必须与早前在build.gradle中声明的类保持一致。

结论

虽然只展示了两个简单的例子,但笔者希望大家能知道:Lint 是非常强大的。只是你要编写适合自己的规则。

本文只展示了两种类型(Detector/Scanner),还有许多其他类型:GradleScannerOtherFileScanner等着你发现。多多尝试,找到最适合你的类。

笔者建议,在编写自定义规则之前,首先阅读系统 Lint 规则,从而帮助你理解其用处及用法。其源码可以在此处下载。

最后,Lint 能帮助你解决开发中的错误,请一定要用哦!

Find below all materials that helped me:

以下为笔者的参考资料:

原文地址:http://jeremie-martinez.com/2015/12/15/custom-lint-rules/

OneAPM Mobile Insight,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

自定义 Lint 规则简介的更多相关文章

  1. 团队项目必备神器——自定义Lint

    Lint 在android studio中内置了大概200个左右的lint检查,比如定义变量未使用,直接Handler报内存泄漏提醒,时时刻刻在监督着我们的代码.自己定制了一些Lint规则,项目开源在 ...

  2. 微信分享网页时自定义缩略图和简介(&period;net版本)

    要实现微信分享网页时自定义缩略图和简介,需开发者在公众平台网站中创建公众号.获取接口权限后,通过微信JS-SDK的分享接口,来实现微信分享功能. 下面来说明实现步骤. 第一部分 准备步骤 步骤一:注册 ...

  3. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造*”了,但博主还是觉 ...

  4. struts2 自定义校验规则

    自定义校验规则:(了解) 在Struts2自定义校验规则: 1.实现一个Validator 接口. 2.一般开发中继承ValidatorSupport 或者 FieldValidatorSupport ...

  5. yii2中自定义验证规则rules

    作者:白狼 出处:www.manks.top/article/yii2_custom_rules 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追 ...

  6. CI 框架中的自定义路由规则

    在 CI 框架中,一个 URL 和它对应的控制器中的类以及类中的方法是一一对应的,如: www.test.com/user/info/zhaoyingnan 其中 user 对应的就是控制器中的 us ...

  7. easyui的validatebox重写自定义验证规则的几个实例

    validatebox已经实现的几个规则: 验证规则是根据使用需求和验证类型属性来定义的,这些规则已经实现(easyui API): email:匹配E-Mail的正则表达式规则. url:匹配URL ...

  8. 在ubuntu16&period;04中安装apache2&plus;modsecurity以及自定义WAF规则详解

    一.Modsecurity规则语法示例 SecRule是ModSecurity主要的指令,用于创建安全规则.其基本语法如下: SecRule VARIABLES OPERATOR [ACTIONS] ...

  9. Scarpy 起始url 自定义代理 自定义去重规则

    - start_urls - 内部原理 """ scrapy引擎来爬虫中去起始的URL: 1. 调用start_requests并获取返回值 2. v = iter(返回 ...

随机推荐

  1. 【Tomcat】tomcat报错 removeGeneratedClassFiles failed

    程序放到测试环境一点问题没有,放到正式环境都是问题.总感觉是环境的问题,环境能带来问题,但是不是所有问题都能说是环境带来的. 这点,要改正.还要淡定对待问题.看错误. 程序是不会骗你的.这个问题折磨了 ...

  2. sysfs接口函数到建立&lowbar;DEVICE&lowbar;ATTR

    sysfs接口函数到建立_DEVICE_ATTR 最近在弄Sensor驱动,看过一个某厂家的成品驱动,里面实现的全都是sysfs接口,hal层利用sysfs生成的接口,对Sensor进行操作. 说道s ...

  3. javascript for

    var k = ["a", "b", "c", false]; for(var i = 0, m; m = k[i]; i++) { con ...

  4. ff与ie 的关于js兼容性

    FF的FIREBUG,不仅能测试JS还能检查CSS错误,是一般常用的.但它主要检查FF方面的错误,对IE就无能为力了.要测试IE,就用ieTester,它可以测试IE几乎所有版本(1.0恐怕也用不到测 ...

  5. NSDictionary、NSMutableDictionary基本使用

    郝萌主倾心贡献,尊重作者的劳动成果.请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 游戏官方下 ...

  6. PHP情人:p十几天来学习hp第一天

    我这里是暂时的 Apache web server 和 MY SQL 如WEB,在php-4.3.3下的环境做的程序.当然要简单的构建和訪问查看数据库 PHPMYADMIN 不可少.  以下简介一下P ...

  7. 导致Asp&period;Net站点重启的10个原因

    原文:导致Asp.Net站点重启的10个原因 Asp.Net站点有时候会莫名其妙的重启,什么原因导致的却不得而知,经过一番折腾后,我总结了导致Asp.Net站点重启的10个原因 1. 回收应用程序池会 ...

  8. pc浏览器css和js计算浏览器宽度的差异以及和滚动条的关系

    如图: css宽度:1250 不包括滚动条宽度 用控制台箭头选取元素显示的左边的宽度:1250  不包含滚动条宽度 缩放浏览器右上角显示的宽度:1267 包含了滚动条宽度 再看下控制台: 由此可计算浏 ...

  9. Centos7下用FastDFS搭建图片服务器

    1.所用到的工具: 1.FastDFS_v5.05.tar 2.fastdfs-nginx-module_v1.16.tar 3.libfastcommonV1.0.7.tar 4.nginx-1.1 ...

  10. android-基础编程-ListView

    ListView主要包括view和数据源.其数据适配器列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter. ListView的没有oom原因.经 ...