关于静态代码扫描工具
在我们项目迭代过程中,线上问题频繁发生。开发时很容易写出一些问题代码,例如 Serializable 的使用:实现了 Serializable 接口的类,如果其成员变量引用的对象没有实现 Serializable 接口,序列化时就会 Crash。再例如,如果 XML 资源文件包含未使用的命名空间,则不仅占用空间,还会导致不必要的处理。其他结构问题,例如使用目标 API 版本不支持的已弃用的元素或 API 调用等,可能导致代码无法正常运行。所以为了进一步减少问题发生,我们逐步完善了一些规范,包括制定代码规范,加强代码 Review,完善测试流程等。但这些措施仍然存在各种不足,包括代码规范难以实施,沟通成本高,因此其效果有限,相似问题仍然不时发生。
有没有办法从技术角度减少或减轻上述问题呢?
我们调研发现,静态代码检查是一个很好的思路。静态代码检查框架有很多种,例如 FindBugs、PMD、Coverity,主要用于检查 Java 源文件或 class 文件;再例如 Checkstyle,主要关注代码风格;但我们最终选择从 Lint 框架入手,因为它有诸多优势:
-
Lint 工具可检查 Android 项目源文件是否包含潜在错误,以及在正确性、安全性、性能、易用性、便利性和国际化方面是否需要优化改进。并且支持 class 文件、资源文件、Gradle 等文件的检查。
-
扩展性强,支持开发自定义 Lint 规则
-
配套工具完善,Android Studio、Android Gradle 插件原生支持Lint工具。
-
Lint 专为 Android 设计,原生提供了几百个实用的 Android 相关检查规则。
Lint的简单使用
一、Lint 与 IDE 的结合使用
点击 Analyze 的 Inspect Code 选项,即可开启 lint 检查,在Inspection窗口中可以看到lint检查的结果。并且 android 自带的lint规则的更改可以在 Setting 的 Edit 选项下选择 Inspections(File > Settings > Project Settings),对已有的 lint 规则进行自定义选择。
二、Lint 与 gradle 命令的结合使用
AS 的控制台,进入要使用 Lint 检查规则的模块目录,使用 gradle lint 命令。输出的结果会生成一个 xml 以及 HTML(可以在浏览器打开,页面非常简单直观)。路径信息也可以在控制台看到,比如我的就是:
Wrote HTML report to file:///D:/cccx_3.0/app/build/reports/lint-results.html
Wrote XML report to file:///D:/cccx_3.0/app/build/reports/lint-results.xml
具体位置如下图:
生成的HTML在浏览器打开如图:
自定义 Lint
为什么需要自定义 Lint?
由于每个业务线自身的需求,Lint 默认的检查项目可能不能满足我们的需求。 比如司机端一个自定义控件需要抽成一个库给其他项目使用,但是我们希望使用者必须在 XML
中定义一个属性,否则组件无法正常运行,我们希望Lint能够对此进行检查,并在忘记添加此属性时给出明确的错误提示。
再比如,我们的基础组件有一个日志库,能够方便的在 release 版本中关闭日志输出,还能够把日志输出到指定的文件中方便事后分析,这时如果来了一个新同学,他可能还是习惯性的用 android.util.Log
来打印日志,我们希望能够检测到本项目中所有使用了 android.util.Log
的代码,并发出警告。 要满足这些自定义需求,我们就需要通过 Android Lint 的扩展机制自己定制 Lint 规则。
Lint 需要自定义检查的问题
一、Crash 预防
Crash 率是我们司机端和乘客端的最重要的指标之一,我们期望使用 Lint 检查出一些潜在的 Crash,例如:
- 原生的
NewApi
,用于检查代码中是否调用了 Android 高版本才提供的 API。在低版本设备中调用高版本 API 会导致 Crash。 - 实现了
Serializable
接口的类,如果其成员变量引用的对象没有实现Serializable
接口,序列化时就会Crash。 - 调用
Color.parseColor()
方法解析后台下发的颜色时,颜色字符串格式不正确会导致IllegalArgumentException
。
二、Bug 预防
由于目前 Bug 数已经作为部门衡量的指标,我们也期望使用 Lint 来检车和预防,例如:
- 所有的页面跳转使用统一的路由
UXRouter
,并且PATH
维护在统一的常量类里方便查阅和修改。 - 使用 Fastjson 解析 JSON 数据时,用基础类型来接收的不要用get包装类型的方法。比如:
getInteger()
和getIntValue()
。
三、性能/安全问题
对于司机端和乘客端来讲,性能和安全非常重要。我们期望使用Lint来检测一些可以规避的影响性能和安全的问题,例如:
- 使用 PendingIntent 时,使用了空 Intent 会导致恶意用户劫持修改 Intent 的内容。所以使用 PendingIntent 时,禁止使用空 intent,同时禁止使用隐式 Intent。
- 在 Android API level 8 以后增加了
android:allowBackup
属性值。默认情况下这个属性值为 true,故
当 allowBackup 标志值为 true 时,即可通过 adb backup 和 adb restore 来备份和恢复应用程序数据。所以就需要强制将android:allowbackup
属性设置为 false,防止 adb backup 导出数据。
四、代码编写规范
对于代码的编写规范之前已经讨论过并且给出了具体的实行方案,我们希望使用 Lint 来检测以便于