2.2 使用Android Lint进行代码审查
Android Lint已经默认集成在IDE中,通过运行Lint来检查代码,从而发现隐藏在代码中疑似的质量问题,随后再通过人工复查更正这些问题。
运行Lint无须执行App,也无须编写额外的测试用例。它可以自动生成报告,还可以通过自定义的方式降低或提高检测问题的严重级别或添加忽略检查项。默认配置下,当我们执行编译操作时,Android Studio会自动运行Lint检查。当然,我们也可以在需要时手动运行检查。
2.2.1 Android Lint概述及基本概念
Android Lint可以检查出Android App代码中的隐含问题,这些问题主要分为6类:正确性(Correctness)、安全性(Security)、性能(Performance)、易用性(Usability)、无障碍性(Accessbility)以及国际化(l18n)。默认情况下,当使用Android Studio进行编译时,会自动运行Lint检查。图2.10展示了Lint的工作原理。
图2.10 Lint的工作原理
App源代码指整个工程的源代码文件,除了Java、Kotlin、XML外,还包括应用图标素材文件以及用于代码混淆的ProGuard配置文件。lint.xml文件是Lint检查的配置文件,当我们需要自定义检查规则时,通常会编辑这个文件。Lint工具是一个静态代码审查工具,可以从命令行或Android Studio中启动它,这一步通常会在发布产品前进行。Lint分析结果分类是Lint工具的检查结果,它覆盖了图2.10中展示的6类问题。
2.2.2 运行Lint检查
我们可以在任何时刻、不同环境下运行Lint检查。
启动Android Studio,按照默认工程配置新建一个名为My Application的工程,然后打开Android Studio中的终端(Terminal),输入gradlew lint,按回车键,即可开始运行。首次运行会耗费一些时间下载JAR包,结束后会以HTML和XML形式给出检查结果,如图2.11所示。
图2.11 从命令行运行Lint
用浏览器打开HTML格式的报告,可看到所有疑似问题,如图2.12所示。
图2.12 Lint报告示例
此外,对于不同的编译版本,还可以进行特定的检查。在My Application工程中,打开appModule的build.gradle文件。可以看到,在默认情况下,只有一个release版本的编译配置,如图2.13所示。
图2.13 Lint报告示例
实际上,还有一个Debug版本类型,这个类型是Android Studio自动配置的。如果我们想重新定义Debug版本的编译配置,就可以在buildTypes节点下显式定义Debug版本类型。当我们需要仅检查Debug版本时,可执行gradlew lintDebug,其他版本同理。
随着程序逻辑日益复杂,代码量日益增多,我们还有可能需要只审查部分代码,或者采用不同的配置文件。这种情况下,使用集成在Android Studio中的Lint工具更加方便。
依次单击菜单栏中的分析(Analyze),在弹出的菜单中选择代码检查(Inspect Code...),在弹出的窗口中可以选取代码检查范围和配置文件,如图2.14所示。
图2.14 在Android Studio中运行Lint检查
默认情况下,检查的项目和在命令行启动检查的项目大体一致。不同的是,在Android Studio中,执行代码检查的结果需在Inspect Results视图中查看,如图2.15所示。
图2.15 在Android Studio中查看Lint检查结果
由图2.15可见,其结果和命令行方式检查的结果是一致的,并且更加直观。在修改代码时也会更加方便,如图2.16所示。
图2.16 在Inspect Results视图中快速修改代码
由此可见,可视化的Lint检查更为简便。
2.2.3 自定义Lint检查范围
接下来,我们来探讨如何自定义Lint的检查范围。在Android Studio中,已经预设了多个检查范围供我们选择,为方便扩展和自定义检查需求,还开放了自定义Lint检查范围,主要通过广泛使用的Scope窗口实现。下面让我们逐一了解它们吧。
1.使用预置的检查范围
回顾图2.14,在Inspection scope部分有4个单选框可供选择:Whole project表示整个工程;Module 'App'表示只检查App模块;Directory'...\app[app]'和当前被打开的文件有关,表示只检查当前被打开的文件;而Custom scope表示自定义范围,我们重点关注它。
选中Custom scope,展开旁边的下拉菜单,可以看到预置的检查范围,如图2.17所示。
图2.17 预置的检查范围
从图2.17中可以看到,有多个菜单项可供选择,我们先了解一下它们都代表什么含义。
· Project Files:当前工程的所有文件。
· Project Source Files:当前工程的所有源代码文件。
· Project Production Files:当前工程中的所有生产文件。
· Project Test Files:当前工程中的所有测试文件。
· Scratches and Consoles:提供了两种临时的文件编辑环境,通常用来存放文本或代码片,该选项在实际开发中很少用到。
· Module 'app':仅app模块的文件。
· Class Hierarchy:当我们选取这个菜单项并单击OK按钮时,会弹出新的窗口,窗口里显示当前工程中的所有类。我们可以使用窗口里的搜索功能过滤要检查的类。在未过滤的情形下,Lint会检查所有类。以Activity作为过滤文本筛选出类,由于新建的工程仅包含一个Activity类,因此筛选结果如图2.18所示。
图2.18 筛选结果示例
2.创建自定义检查范围
当预设的检查范围无法满足我们的自定义需求时,可以进一步对代码检查范围进行自定义。
单击图2.17中下拉菜单右侧的三个点按钮,弹出Scope窗口(见图2.19),我们在这里配置自定义检查范围。
图2.19 Scope窗口
注意:这里的Scope配置也可用于其他功能,比如搜索。
默认情况下,Scope配置为空。单击界面左上角的“+”,然后选择Local来添加新范围。
Local和Share的共同点是给定代码检查范围,区别是Share还可用于具有范围字段的其他项目。也就是说,Local类型的Scope配置是个人使用的,保存在个人的workspace中,默认保存在/config/projects/<project_name>/.idea/workspace.xml中;Share类型的Scope配置是整个工程的,可以通过版本控制系统被团队成员共享,它的默认路径在/config/projects/<project_name>/.idea/scopes/中。
此处我们以Local类型的Scope为例进行介绍,名称为UIView,即仅检查和UI视图相关的类。
仔细观察图2.20中的内容,Pattern的含义是正则表达式,如果读者对正则表达式比较熟悉,那么完全可以直接填写合法的正则表达式,达到定义范围的目的。
图2.20 名称为UIView的Scope配置
或者使用窗口右侧的4个按钮来控制检查范围,分别说明如下:
· Include:包含此文件夹及其文件,不包含子文件夹中的内容。
· Include Recursively:包含此文件夹及其文件,递归包含所有子文件夹及其文件。
· Exclude:排除此文件夹及其文件,不递归排除所有子文件夹及其文件。
· Exclude Recursively:排除此文件夹及其所有文件,递归排除所有子文件夹及其文件。
通过对照可轻松地发现,图2.20中,我们选择了MainActivity.java和activity_main.xml两个文件。最后,单击OK按钮确认。
2.2.4 自定义Lint检查类型
2.2.3小节中,我们主要针对Lint的检查范围做了详细的说明,本小节主要介绍Lint的检查类型。这里介绍3种自定义Lint检查类型的方法,分别对应不同的需求。读者可根据实际项目的需要结合使用。
1.使用和自定义Lint配置文件
在Android Lint中内置了多种静态代码检查的配置文件。我们可以直接使用它们,也可以更改它们的名称、说明、范围甚至是严重级别,也可以随时启用或禁用某个配置文件,达到跳过某种检查的目的。
我们先来看默认情况下会进行哪些类型的检查。
打开图2.14所示的窗口,选择Inspection Profile部分中的三个点按钮,出现Inspections窗口,如图2.21所示。
图2.21 Inspections窗口
检查的项目、说明以及启用状态等信息一目了然地罗列在这个窗口中。我们除了可以通过复选框启用/禁用某个检查外,还需关注一个地方,就是位于左上角的Profile下拉菜单。
展开Profile下拉菜单,默认情况下,预置了Default和Project Default两个配置。显而易见,前者是对整个Android Studio而言的,将影响所有的工程;后者是对单个工程而言的。在未做任何自定义配置的前提下,这二者是相同的。
同时,我们还可以单击下拉菜单右侧的小齿轮,添加更多自定义的菜单项。由Default复制而来的配置依旧影响所有工程,由Project Default复制而来的配置则仅对当前工程有效。当然,我们还可以对其进行重命名、删除、导入以及导出等操作。
2.配置lint.xml文件
通过前面的学习,我们已经可以实现特定范围、特定检查种类的自定义了。看上去似乎已经满足了静态代码检查的需要,事实上也确实如此。那为什么这里还要介绍lint.xml文件呢?
想象这样一种情况,假设我们的工程有多个XML布局文件,要求某个或某几个布局文件需要单独定义检查类型。根据现有的知识,我们需要定义不止一个Scope,然后定义不止一个Profile,最后挨个启动检查。是不是很烦琐?有没有办法简化呢?答案是肯定的——借助lint.xml定义规则,即可完成快捷方便的检查。
lint.xml位于整个工程根目录下,默认不会自动创建,需要我们手动添加这个文件,格式遵循标准的XML,一个规则定义的范例如下:
可见,lint.xml文件由封闭的<lint>标记包裹,其中包含多个<issue>子元素,每个<issue>子元素定义了唯一id。
想要理解如何定义issue子元素并不难,如以上代码片段所示,其中包含4个issue子元素,分别是忽略了所有IconMissingDensityFolder类型的检查,对不同XML文件进行不同问题类型的忽略,不包含任何自组件的View,以及设置了HardcodedText问题类型级别为error。当Lint检查被执行时,以上配置将生效。完整的问题id列表以及对应的描述可以通过执行lint –list查看,该文件位于[Android Sdk目录]\tools\bin\。
3.在源代码文件中添加忽略项
除了上述在lint.xml中定义检查规则外,我们还可以直接在源代码文件中添加指定的忽略规则,支持Java、Kotlin和XML三种类型的源代码。接下来,我们对上述3类代码分别进行讲解。
(1)Java/Kotlin
首先来看Java/Kotlin,当我们需要对类中某个方法进行忽略规则的定义时,在方法声明前添加注解即可,参考下面的代码片段:
Java语言:
Kotlin语言:
当我们需要对整个类进行忽略规则的定义时,在类声明前添加注解即可,参考如下代码片段:
Java语言:
Kotlin语言:
特别地,当我们需要排除整个类的所有类型的检查时,可按照如下方式添加注解:
(2)XML
在对XML文件添加排除项前,需要先添加如下定义命名空间,以便Lint工具识别添加的属性:
当我们需要添加排除项时,只需在对应的布局节点处使用tools:ignore属性即可,参考下面的代码片段:
上述代码中,我们为LinearLayout添加了排除项UnusedResources和StringFormatInvalid。其中的子元素TextView会受其父元素的影响,也将排除相对应类型的检查。
和Java/Kotlin类似,要排除所有的检查项,使用all关键字即可。
4.在整个Module中添加忽略项
某些情况下,整个工程可能包含多于一个Module,统一的检查规则可能不适用于所有Module。因此,我们需要一种方法对单个Module进行规则定义,秘诀就在于每个Module的build.gradle文件。
要定义某个Module的检查规则是很容易的,只需在android节点下添加lintOptions代码块即可。我们将下面的代码片段加入之前新创建的My Application工程的app模块对应的build.gradle文件内:
由于build.gradle文件发生了更改,因此需要Sync才能使这些更改生效。
再次运行Lint检查,得到如图2.22所示的结果。
图2.22 添加忽略规则的检查结果
与图2.15对比发现,有关旧版本库的warning已经消失不见了,这正是排除了GradleDependency规则的结果——不对仍然使用旧版本库进行警告。