一、Android Lint
-
Lint简介
Android Lint 是有 Android SDK 提供的一种静态代码检测工具,用于检测 Android 的代码质量。Android Lint 的源码集成在 Android SDK Tools 16 及更高的版本中,我们可以在项目目录下通过 ./gradlew lint 命令调用,也可以通过 Android Studio 的 【Analyze】->【Inspect Code】路径调用 Lint 检查。
Lint 是 Android 提供的一个强大的,用于静态扫描应用源码并找出其中的潜在问题的实用工具。lint 工具可以检查你的 Android 项目源文件是否有潜在的错误,以及在正确性、安全性、性能、易用性、无障碍性和国际化方面是否需要优化改进。
Lint 既可以用作命令行工具,也可以与 Eclipse 和 IntelliJ 集成在一起。它被设计成独立于 IDE 的工具,我们可以在 Android Studio 中非常方便的使用它。
-
Lint的工作流
-
Lint的问题等级
Fatal严重致命的、Error错误、Warning警告、Information提示、Ignore忽略 不提示
实际测试Fatal、Error只是错误代码部分增加红色和下划线提示,不会报错和阻断编译。
-
Lint规则使用方法
1、配置Gralde,build.gradle 文件的android下增加lintOptions,下面列举 lintOptions 可定义的选项
android {
lintOptions {
// true--关闭lint报告的分析进度
quiet true
// true--错误发生后停止gradle构建
abortOnError false
// true--只报告error
ignoreWarnings true
// true--忽略有错误的文件的全/绝对路径(默认是true)
//absolutePaths true
// true--检查所有问题点,包含其他默认关闭项
checkAllWarnings true
// true--所有warning当做error
warningsAsErrors true
// 关闭指定问题检查
disable 'TypographyFractions', 'TypographyQuotes'
// 打开指定问题检查
enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
// 仅检查指定问题
check 'NewApi', 'InlinedApi'
// true--error输出文件不包含源码行号
noLines true
// true--显示错误的所有发生位置,不截取
showAll true
// 回退lint设置(默认规则)
lintConfig file("default-lint.xml")
// true--生成txt格式报告(默认false)
textReport true
// 重定向输出;可以是文件或'stdout'
textOutput 'stdout'
// true--生成XML格式报告
xmlReport false
// 指定xml报告文档(默认lint-results.xml)
xmlOutput file("lint-report.xml")
// true--生成HTML报告(带问题解释,源码位置,等)
htmlReport true
// html报告可选路径(构建器默认是lint-results.html )
htmlOutput file("lint-report.html")
// true--所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
checkReleaseBuilds true
// 在发布版本编译时检查(即使不包含lint目标),指定问题的规则生成崩溃
fatal 'NewApi', 'InlineApi'
// 指定问题的规则生成错误
error 'Wakelock', 'TextViewEdits'
// 指定问题的规则生成警告
warning 'ResourceAsColor'
// 忽略指定问题的规则(同关闭检查)
ignore 'TypographyQuotes'
}
}
2、指定规则
// 回退lint设置(默认规则)
lintConfig file("default-lint.xml")
default-lint.xml手动创建放在当前module目录
类似
这种可以单独写在xml中
3、执行Lint检测
分为三种:
第一种,常见的实时检测,AndroidStudio自带的,可以通过lintOption配置显示错误的等级。
测试 我把NewApi和InlineApi这种错误改成Fatal致命的,build同步一下
看代码
只会提示但不会阻挡编译和运行,Error的效果相同。
Warning和Infomational的效果一样,也是只会提示但不会阻挡编译和运行。
忽略检测
指定方法忽略
lint在实时检测只能发挥提示的作用,进一步查看lint规则,找到需要的规则。
下方有专门的规则列表
https://wiki.h3d.com.cn/pages/viewpage.action?pageId=86346982
针对空指针,手动写个报错代码
未赋值实时检测lint是有提示的。
手动赋值为null竟然不显示,下面会依次查找现有规则。
第二种,手动静态扫描
第三种,Gradle命令
- ./gradlew lint
- ./gradlew app:lintDebug
- ./gradlew app:lintTiktokI18nDebug
这些命令,会执行gradle文件,应用lintOptions的配置,进行Lint检测,结果会放在配置中指定的xml或html中。
生成html报告中没有找到空指针相关的提示。
-
Lint规则
Lint规则
官方规则
Android Lint Checks - Android Studio Project Site
我翻译后的规则单独都放在了这里
https://wiki.h3d.com.cn/pages/viewpage.action?pageId=86346982lint规则包含一下几个分类
查找android官方lint库,没有找到类似空指针这种语法级别的规则。
二、Lint自定义规则
1、为什么需要自定义 Lint ?
由于每个业务线自身的需求,Lint 默认的检查项目可能不能满足我们的需求。 比如司机端一个自定义控件需要抽成一个库给其他项目使用,但是我们希望使用者必须在 XML 中定义一个属性,否则组件无法正常运行,我们希望Lint能够对此进行检查,并在忘记添加此属性时给出明确的错误提示。
再比如,我们的基础组件有一个日志库,能够方便的在 release 版本中关闭日志输出,还能够把日志输出到指定的文件中方便事后分析,这时如果来了一个新同学,他可能还是习惯性的用 android.util.Log 来打印日志,我们希望能够检测到本项目中所有使用了 android.util.Log 的代码,并发出警告。 要满足这些自定义需求,我们就需要通过 Android Lint 的扩展机制自己定制 Lint 规则。
同时发现潜在的空指针未赋值等造成Crash的语法问题。
2、lint Api
如果要查看 lint 工具支持的 issue 的完整列表和它们所对应的 issue ID,可以使用 lint --list 命令。
Lint 中包括多种类型的 Scanner 如下,其中最常用的是扫描 Java 源文件和 XML 文件的 Scanner:
SourceCodeScanner:扫描 Java 和kotlin源文件
XmlScanner:扫描 XML 文件
ClassScanner:扫描 class 文件
BinaryResourceScanner:扫描二进制资源文件
ResourceFolderScanner:扫描资源文件夹
GradleScanner:扫描 Gradle 脚本
OtherFileScanner:扫描其他类型文件
使用Detecotor探测器,实现多个scanner扫描,然后找出lint问题,并通过Issue提示出来。
最后通过注册器IssueRegistry,注册到androidLint。
3、自定义lint
3.1、建立lint扫描的module(lint_tools)
类型选Java or Kotlin library,命名lint_tools
包名com.h3d.qqx5.lint,保持现有结构
- build.gradle设置导包
导包
compileOnly "com.android.tools.lint:lint-api:$lint_version"
compileOnly "com.android.tools.lint:lint-checks:$lint_version"
一定要是compileOnly确保只编译不打包进主Module。
我这里使用kotlin的module所以额外有kotlin导包。
- 编写一个代码检查器LogDetector.kt
继承Detector探测器,实现SourceCodeScanner接口,该接口支持扫描java和kotlin类。
看看源码该扫描类的方法
返回此检测器感兴趣的方法名称列表,或者空,也就是该类调用了什么方法,需要设置要检测的方法名,限制了范围。
方法访问,由getApplicableMethodNames()限制的方法名,该方法就可以检测到指定的方法。
比如检查App内调用了系统Log和System.out.println方法的标记并提示报错。
isMemberInClass是检查指定的方法的类名是否存在于该检测类中。存在我们就荣国ISSUE来跟Lint一样的样式提示出来。
参数分别是1、取消检测使用的注解名称 2和3、lint提示 4、Lint类型(性能、Icon、国际化这种,默认Lint)5、优先级1-10 6、报错等级 7、默认实现
编写好的ISSUE需要注册到Lint
- 编写注册类
- 编写module的build.gradle,全局配置文件设置注册类
3.2、主module使用lint_tools
由于安卓官方推荐的方式是把lint_tools打成jar,然后命名为lint.jar直接放到开发者C盘的.android/lint目录下使用,这将导致所有工程都要使用该lint,没必要。
lint_tools是lib工程只会生成jar,如果将该lint_tools直接导入主module,因为module是以jar的形式,无法直接发布,需要以aar形式引入。
这就需要中间module作为aar,创建Android Module (aar) lint_check
build.gradle下发布lint_tools module
其他选项和主module保持一致
主Module导入
gradle同步一下,以后build或者编译或者lint运行都可以实时检测。
但是空指针怎么判断还没有具体方法,继续研究~
改造LogDetetor,设想检测方法,所有get开头的方法判断获取到的内容是否为空
但是发现并没有错误提示。
后来查看api注释,只有添加的调用方法名,才能被Lint检查,Log之所以可以是因为它是系统Api,被广泛调用。也就是说已经被调用的通用代码可以使用,但是空指针一般都是未知的方法。
尝试加入
必须明文传入的方法才可以被检测,所以空指针的Lint暂时未找到实现方式。
-
Java代码增加空安全语法包裹
设计思路:传入原始数据,返回不为空的数据
类型必须要使用到泛型,因为Java的泛型实例化也是必须使用class类型的,由于data如果为空是不可能拿到class的,所以还需要额外的class传入,泛型这里是class<T>
对一个可能的数据空,调用该方法,除非实例化异常,否则很难出空指针异常了。