管中窥豹:结合 NewApi 实践来了解 Lint 代码扫描

595 阅读6分钟
原文链接: mp.weixin.qq.com

导读

lint是著名的C/C++语言静态代码分析工具之一,Android Lint顾名思义,针对Android的静态代码分析工具,能够对Android项目中潜在的bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查。


在Android SDK Tools 16及更高的版本中,Lint工具会自动安装。通过对Android工程源代码等进行扫描检查,可发现潜在的问题,更好的提升代码质量。Android Lint提供了命令行方式执行,也与IDE(如Eclipse、Android Studio)集成提供了IDE图形界面,单独输出的xml和html结果报告可以提供更丰富的信息。


lint扫描规则初览
1 NewApi规则说明

初步扫描手管代码得到一份html的报告,结果分类比较清晰,但有2000+error,12000+warning,,吓的手一抖直接关掉了。。

为了降低大家的心理门槛,我们从手管已经接入Daily的NewApi规则来看看lint是怎么工作的


最简单的,先来看看NewApi规则说明: 可以对NewApi规则有个大致的了解,扫描App中的Android Api,对起始版本大于AndroidManifest.xml中声明的minSdkVersion,即未加判断调用的高版本Api进行提示。没有判断调用不支持的Api会怎么样呢?低版本机器在执行到该代码段时就会抛出NoSuchMethodException异常crash。


2 NewApi扫描结果

再来看看NewApi的扫描结果: 可以看到检查结果中的issueid、summary、priority、severity、category、explanation和说明中是一致的,message中有更详细的代码段接口版本说明,location字段中给出了对应的代码位置。

01

Issueid:规则名,唯一;

02

Summary:规则的简单概述;

03

  Priority:优先级,1~10,10为最严重

04

  Severity:严重性,Fatal,Error,Warning,Information,Ignore

05

Category:类别,Correctness 正确性Security 安全性Performance 性能Usability 可用性Accessibility 可达性i18n 国际化

06

Explanation:规则详细描述及问题解决建议


lint规则实现原理篇
1 lint规则主要模块

从NewApi在手管的落地实践来看NewApi确实是解决Api版本兼容性的一大利器,lint是怎么实现这一规则的呢?lint支持的280+规则都是怎么实现的呢?


我们来看看lint规则的主要模块:


01

Issue:lint规则定义,比如NewApi,lint已有规则列表维护在BuiltinIssueRegistry类中,目前lint官网提供有280+个规则,可以按需打开也可以修改各个规则的严重级别,已有规则配置可以见实践篇;

02

Detetor:检索项目中检测项对应的问题,一个检测器可以检索多个独立但相关的问题,比如通过一个检测器查找多种Manifest相关的问题;

03

Implematation:连接检查项和检测器,也声明规则的查找范围,常用的scope包括CLASS_FILE,JAVA_FILE,RESOURCE_FILE等;

04

Registry:注册模块,lint维护了一张所有规则的列表,检查规则通过注册添加到规则列表中;


2 NewApi规则注册类

从NewApi检查项的注册定义可以看到,issueid、summary等均在issue注册时传入以便在结果报告中展示,Implematations中scope声明了规则查找范围,Scope.CLASS_FILE标明了NewApi检查项针对编译后的class字节码进行扫描:


3 NewApi扫描核心类

再来看看NewApi扫描核心的ApiDetector:


ApiDetector检测器继承自ResourceXmlDetector并实现Detector.ClassScanner和Detector.JavaScanner接口,Detector类中提供了7种XXXScanner接口Scanner也并不是直接进行代码行查找,scanner中通过lombok.ast( Abstract Syntax Tree抽象语法树) API来进行代码节点的查找,有兴趣的童鞋可以参照Eclipse AST介绍。


扫描规则实际上就是实现detector的过程,每个detector可以定义1个或多个不同类型的issue,像ApiDetector中会处理多个Api调用相关的规则:NewApi,InlinedApi,Override,UnusedAttribute;


继续查看ApiDetector最主要的checkClass()可以更深入了解NewApi的扫描过程: 

Api版本库中维护了一份Android每个版本Class的类关系和成员变量,是Api兼容性检测的前提条件


首先进行类扫描处理,如果没有TargetApi定义的局部miniSDK则获取AndroidManifest.xml中minSdkVersion定义,首先进行继承类和接口类的扫描判断,发现的问题通过report()函数输出:


扫描结果-类兼容问题



然后开始对类节点的扫描处理,同样判断方法前是否有TargetApi标注定义了局部miniSdk,依次检查类中method、field、LDC引用值,源码中可以看到在method、field的调用判断中,也对android常用的版本判断格式if(Build.VERSION.SDK_INT >= XX)的分支进行判断检查


对应的扫描结果中message字段返回了兼容性调用问题的类型及起始版本,并将发现的问题通过report()函数输出。


扫描结果-Field调用兼容问题:


扫描结果-method调用兼容问题:


自定义扫描规则篇

通过走读lintNewApi的实现过程,我们也清楚了lint中的规则是如何定义并实现的,我们自己是否也可以参照这个结构来自定义规则呢?答案是肯定的,lint也支持自定义规则扩展,自定义规则通过IssueRegistry加入到规则表中和其他规则一起使用。什么场景适合自定义规则呢?比如手管UI库的编写规范,典型问题的修复情况,某些封装了不建议直接使用的Api的调用等都可以通过自定义规则来规范和提醒。


自定义lint规则是以jar形式存在的,通过继承lint的两个类来实现规则扩展:


继承IssueRegistry:自定义Lint规则的主类,有且只有一个,注册这个自定义Lint项目中有哪些自定义的issue:


继承Detector并实现Detector中合适的XXXScanner接口:可以根据需求实现多个自定义Detector类,在每个Detector类中实现自定义的一个或多个issue;

在eclipse中新建java工程并引用sdk\tools\lib\lint-api.jar包,手动添加导出配置MANIFEST.MF文件 export导出jar包,生成的jar包放到~/.android/lint/路径下,此时调用命令行工具就可以看到我们自定义的规则了


总结篇

管中窥豹,走读已有规则的实现可以让我们对工具有更全面的了解,更好的应用到项目中,网上关于自定义规则的示例也不多,源码中的规则实现也是一个很好的参照途径,也需要我们更进一步分析代码问题挖掘个中需求,才能发挥工具的更大作用。


本文简单结合手机管家NewApi的实践来了解Lint代码扫描过程,期待大家一起来探讨代码扫描工具有哪些更有价值的应用场景呢?


参考资料:


[1] http://tools.android.com/tips/lint/writing-a-lint-check 


[2]https://android.googlesource.com/platform/tools/base/+/master/lint/ 


[3]https://www.bignerdranch.com/blog/building-custom-lint-checks-in-android/


[4] Android Lint: 静态检查Android版本兼容性问题


[5]Android Lint工作原理剖析