自定义 Lint 规则简介
2016-02-26 17:16
429 查看
上个月,笔者在巴黎 Droidcon 的 BarCamp 研讨会上聆听了 Matthew Compton 关于编写自己的 Lint 规则的讲话。深受启发之后,笔者想就此话题做进一步的探索。
Lint 是一款静态代码分析工具,能检查安卓项目的源文件,从而查找潜在的程序错误以及优化提升的方案。
当你忘记在
Lint 易于使用,通过简单的 Gradle 任务:
在简单的介绍之后,笔者希望大家能达成共识:Lint 是理解一些安卓 API 框架使用情况的好帮手。
如果你在写一个代码库/SDK,你想帮助开发者正确地使用它,Lint 规则就能派上用场。有了 Lint,你可以轻易地提醒他们忽略或做错的事情。
如果你的团队有了新加入的开发者,Lint 可以帮助他快速了解团队的最佳实践,或命名惯例。
如你所见,为了实现自定义 Lint 规则,需要两个编译依赖关系。此外,还需要确切的 Lint-Registry,后文会介绍这是什么,现在只需记住这是强制要求。最后,创建一个小任务来快速安装新的 Lint 规则。
接着,使用
配置好模块之后,让我们来看看如何编写第一条规则。
如你所见,我们继承了
- 第一个
- 第二个
-
-
过滤之后,我们使用简单的算法实现
其中,每个参数都很重要,而且是强制性参数。
- AttrNotPrefixed 是 Lint 规则的 id,必须是唯一的。
-
-
-
-
-
你可能也发现了,其实所需的代码非常简单易懂。你只需小心所用的范围以及为
Lint 报告可能得出的结果如下:
如你所见,规则二的模式与规则一相同。方法
最后,改变
虽然问题描述可能不很清楚,但一个
规则二的 Lint 报告结果举例如下:
如你所见,登记过程也非常简单。我们只需继承
本文只展示了两种类型(
笔者建议,在编写自定义规则之前,首先阅读系统 Lint 规则,从而帮助你理解其用处及用法。其源码可以在此处下载。
最后,Lint 能帮助你解决开发中的错误,请一定要用哦!
Find below all materials that helped me:
以下为笔者的参考资料:
https://github.com/bignerdranch/linette
https://speakerdeck.com/ambergleam/linty-fresh
Source code
http://tools.android.com/tips/lint-custom-rules
https://github.com/googlesamples/android-custom-lint-rules
原文地址:http://jeremie-martinez.com/2015/12/15/custom-lint-rules/
OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
定义
如果你是安卓开发者,那你一定已经知道 Lint 的定义。Lint 是一款静态代码分析工具,能检查安卓项目的源文件,从而查找潜在的程序错误以及优化提升的方案。
当你忘记在
Toast上调用
show()时,Lint 就会提醒你。它也会确保你的
ImageView中添加了
contentDescription,以支持可用性。类似的例子还有成千上万个。诚然,Lint 能在诸多方面提供帮助,包括:正确性,安全,性能,易用性,可用性,国际化等等。
Lint 易于使用,通过简单的 Gradle 任务:
./gradlew lint就能在任意安卓项目上运行。它会生成一份报告,指出它的发现并按照种类、优先级和严重程度对问题进行分类。这份报告能确保代码质量,防止 app 中出现代码错误,因此应该时刻进行监控。
在简单的介绍之后,笔者希望大家能达成共识:Lint 是理解一些安卓 API 框架使用情况的好帮手。
为什么要自己写 Lint 规则?
大多数开发者可能都不知道:你可以自己写 Lint 规则。其实,在很多使用案例中,自定义的 Lint 规则往往大有用处:如果你在写一个代码库/SDK,你想帮助开发者正确地使用它,Lint 规则就能派上用场。有了 Lint,你可以轻易地提醒他们忽略或做错的事情。
如果你的团队有了新加入的开发者,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 clashes.", 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方法会只保留
attrXML 元素。
-
getApplicableAttributes方法会只保留
nameXML 属性。
过滤之后,我们使用简单的算法实现
visitElement方法。一旦发现某个
attrXML 标记的
name属性不源自安卓也不以
ct前缀,我们就报告一个
Issue。该
Issue按照如下方式声明在类的头部:
public static final Issue ISSUE = Issue.create("AttrNotPrefixed", "You must prefix your custom attr by `ct`", "To avoid clashes, 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 clashes, we prefixed all our attrs.(为避免冲突,所有属性均添加前缀。)是更为详细的解释。
-
5是优先级系数。必须是1到10之间的某个值。
-
WARNING是严重程度。此处我们只选择
WARNING,这样即便存在该问题,代码也能安全运行。
-
Implementation是
Detector间的桥梁,用于发现问题。
Scope则用于分析问题。在本例中,我们必须处于资源文件层面才能分析前缀问题。
你可能也发现了,其实所需的代码非常简单易懂。你只需小心所用的范围以及为
Issue输入的值即可。
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`"); } } }
如你所见,规则二的模式与规则一相同。方法
getApplicableCallNames与
getApplicableMethodNames用于明确寻找的目标。之后,我们找出问题并创建之。唯一的不同在于,我们不再继承
XmlResourceDetector类,而是仅继承
Detector类,并实现
ClassScanner接口以处理 Java 类检查。所以,实际上,规则二的变化没有很多。如果仔细查看
XmlResourceDetector类,会发现它只是实现
XmlScanner的
Detector类。因此,所有规则都适用的总结如下:我们只需继承
Detector并实现合适的
Scanner接口即可。
最后,改变
Issue的范围并关闭
CLASS_FILE_SCOPE。此处,要想找到问题,只需分析一个 Java 类文件即可。有时,你需要分析多个 Java 类文件才能发现问题,所以你需要使用
ALL_CLASS_FILES。范围的选择非常重要,因此请小心谨慎。点击此处可查看全部范围。
虽然问题描述可能不很清楚,但一个
Detector可以发现多个问题。此外,通过一次运行就能处理所有问题,因此可以有效提高应用性能。
规则二的 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),还有许多其他类型:
GradleScanner,
OtherFileScanner等着你发现。多多尝试,找到最适合你的类。
笔者建议,在编写自定义规则之前,首先阅读系统 Lint 规则,从而帮助你理解其用处及用法。其源码可以在此处下载。
最后,Lint 能帮助你解决开发中的错误,请一定要用哦!
Find below all materials that helped me:
以下为笔者的参考资料:
https://github.com/bignerdranch/linette
https://speakerdeck.com/ambergleam/linty-fresh
Source code
http://tools.android.com/tips/lint-custom-rules
https://github.com/googlesamples/android-custom-lint-rules
原文地址:http://jeremie-martinez.com/2015/12/15/custom-lint-rules/
OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
相关文章推荐
- 深入理解PHP7内核之FAST_ZPP
- 使用zabbix监控Nginx活动状态--Part1
- Flex 性能优化常用手法总结
- oracle 性能优化建议小结
- C#实现简单屏幕监控的方法
- Lua性能优化技巧(一):前言
- Lua性能优化技巧(五):削减、重用和回收
- Lua性能优化技巧(三):关于表
- Lua性能优化技巧(四):关于字符串
- C#进程监控方法实例分析
- Windows下使用性能监视器监控SqlServer的常见指标
- SQL Server 2016 查询存储性能优化小结
- MySQL性能优化 出题业务SQL优化
- PowerShell脚本性能优化技巧总结
- SQL SERVER性能优化综述(很好的总结,不要错过哦)第1/3页
- MySQL Index Condition Pushdown(ICP)性能优化方法实例
- Ajax无刷新分页的性能优化方法
- Android编程实现监控各个程序流量的方法
- Zabbix监控Linux主机设置方法
- Zabbix监控交换机设置方法