Android静态代码检查及自定义Lint实现

概述


在日常的项目开发迭代中,相信每个人对与代码质量都是有着高要求的。但是,在所有事情中,人往往就是其中最大的变量因素,个人各异,如何去保障代码质量以及统一规范呢?开发团队也许会严格要求Code-Review以及MR来把控前期质量,也许会要求大量的自动化测试、单元测试、人工测试等来保障业务稳定性,也许还会集成异常捕获及时发现并修复线上问题。。。
任何的问题修复都需要有对应资源的付出,问题发现的越早,付出的成本也就越低。在 STICKYMINDS 网站上的一篇名为 《 The Shift-Left Approach to Software Testing 》 的文章中提到,假如在编码阶段发现的缺陷只需要 1 分钟就能解决,那么单元测试阶段需要 4 分钟,功能测试阶段需要 10 分钟,系统测试阶段需要 40 分钟,而到了发布之后可能就需要 640 分钟来修复 。
在这里插入图片描述

那有没有什么方法可以帮助我们更早地发现问题呢?有!那就是静态代码检查!

 

1、什么是静态代码检查

 

根据维基百科介绍,静态代码检查又称静态程序分析(英语:Static program analysis)是指在不运行程序的条件下,进行程序分析的方法。静态代码检查工具会以源代码为检查对象,从命名、语法、语义等多个维度进行扫描分析,发现可能存在的问题,并根据检查规则对问题进行严重等级划分,给出不同的标识和提示。
因为静态代码检查的对象为源代码,所以在编码阶段我们就能够发现并修复问题,时间节点的提前对应着修复问题所付出的成本大大降低。
在这里插入图片描述

2、价值

  • 提前发现代码问题,降低修复成本;
  • 相较于动态分析,静态代码检查速度更快;
  • 自定义规则实现,可用于编码规范限制,统一风格;

 

常用静态代码检查工具


不同的平台,不同的语言都会有一种或者多种代码检查工具,如iOS的Clang Static Analyzer、OCLint、Infer,前端的ESLint、JShint,Python的PyCharm等等,这里不做一一举例。下面将简单对比一下在Android开发中常见的几款检查工具。

维度FindBugs/SpotBugsCheckStyleLint
扫描对象Java字节码源代码文件Java、XML、Kotlin、Java字节码、Gradle、图片资源、Manifest文件等
原理基于BCEL库通过扫描字节码完成代码检查,主要做缺陷模式匹配和数据流分析使用Antlr库对源码文件做词语法分析生成抽象语法树,遍历整个语法树匹配检测规则基于抽象语法树分析
内置规则种类300+检测规则100+检测规则300+检测规则
优点针对字节码检查,对JDK定制化程度高,能发现Java代码中潜在的错误和缺陷耗时相对较少、轻量、针对代码风格检查有有优势官方支持、检测全面、扩展性强、支持自定义规则、配套工具完善
缺点定制规则门槛高,依赖编译代码,扫描耗时检查规则相对简单,无法检查潜在问题检测字节码时依赖编译代码,全量检测耗时,版本迭代API较大

 

Lint使用


Android Lint 是 ADT 16(和 Tools 16)中引入的一个新工具,用于静态代码扫描发现 Android 项目源中的潜在错误,以及在正确性、安全性、性能、易用性、无障碍性和国际化方面是否需要优化改进。
Lint 既可以用作命令行工具,也可以与 Eclipse 和 IntelliJ 集成在一起。它被设计成独立于 IDE 的工具,我们可以在 Android Studio 中非常方便的使用它。

 

1、Lint源文件扫描工作流

 
在这里插入图片描述
应用源文件:源文件包含组成 Android 项目的文件,包括 Java、Kotlin 和 XML 文件、图标以及 ProGuard 配置文件。
lint.xml 文件:一个配置文件,可用于指定要排除的任何 lint 检查以及自定义问题严重级别。
lint 工具:一个静态代码扫描工具,您可以从命令行或在 Android Studio 中对 Android 项目运行该工具。lint 工具检查可能会影响 Android 应用的质量和性能的代码结构问题。强烈建议您先更正 lint 检测到的所有错误,然后再发布您的应用。
lint 检查结果:可以在控制台或 Android Studio 的 Inspection Results 窗口中查看 lint 检查结果。

 

2、Lint工具使用

  1. 从菜单栏中,依次选择 Analyze > Inspect Code
    在这里插入图片描述

  2. 在 Specify Inspection Scope 对话框中,查看设置。在 Inspection profile 下,选择配置文件。
    在这里插入图片描述

  3. 结果查看
    在这里插入图片描述

  4. lint扫描结果主要有下面几大类

Accessibility:无障碍,例如 ImageView 缺少 contentDescription 描述,String 编码字符串等问题。
Correctness:正确性,例如 xml 中使用了不正确的属性值,Java 代码中直接使用了超过最低 SDK 要求的 API 等。
Internationalization:国际化,如字符缺少翻译等问题。
Performance:性能,例如在 onMeasure、onDraw 中执行 new,内存泄露,产生了冗余的资源,xml 结构冗余等。
Security:安全性,例如没有使用 HTTPS 连接 Gradle,AndroidManifest 中的权限问题等。
Usability:易用性,例如缺少某些倍数的切图,重复图标等。

 

3、自定义Lint规则实现

 

Android studio 内置了许多Lint规则,但当内置规则无法完全匹配我们的需求时,尤其针对一些编码严谨性要求和编码规范要求,这就需要我们实现Lint规则的自定义;
下面就让我们来看看具体怎么实现规则的自定义吧。

 

3.1 新建module

 
自定义Lint需要一个纯Java项目,Module类型选择Java or Kotlin Library, 暂时命名 lint_customize。

 

3.2 build.gradle依赖配置

 

plugins {id 'java-library'id 'kotlin'id 'kotlin-kapt'
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])compileOnly 'com.android.tools.lint:lint-api:30.2.1'compileOnly 'com.android.tools.lint:lint-checks:30.2.1'
}sourceCompatibility = "1.8"
targetCompatibility = "1.8"jar {manifest {attributes("Lint-Registry-v2": "com.jiang.lint.customize.IMockIssueRegistry")}
}
  1. 模块中添加了Lint相关依赖:
    com.android.tools.lint:lint-api:提供了 Lint 的 API,包括 Context、Project、Detector、Issue、IssueRegistry 等,后面会做介绍;
    com.android.tools.lint:lint-checks:包含了 Lint 支持的200多种规则;
    这里需要注意,只能使用 compileOnly 依赖,使用 implementation 将会编译报错
    在这里插入图片描述

  2. 因为Lint依赖库内部使用了Kotlin,所以这里也需要添加相关插件,否则编译正常,但是自定义的规则却不会生效;

  3. Lint-Registry-v2 注册,将自定义规则注册至Lint;

3.3 API说明

 

在正式开始实现前,我们先来了解几个Lint API:
Issue:问 题的描述,表示一个 Lint 规则。
Detector:中文是探测器,用于检测并报告代码中的 Issue,每个 Issue 都要指定 Detector。
Scope:翻译过来是表示范围的意思。这是用于声明 Detector 要扫描的代码范围,例如 JAVA_FILE_SCOPE、CLASS_FILE_SCOPE、RESOURCE_FILE_SCOPE、GRADLE_SCOPE 等,一个 Issue 可包含一到多个 Scope。
Scanner:翻译过来就是扫描器的意思。用于扫描并发现代码中的 Issue,每个 Detector 可以实现一到多个 Scanner。

Scanner 类型说明
UastScanner扫描 Java、Kotlin 源文件
XmlScanner扫描 XML 文件
ResourceFolderScanner扫描资源文件夹
ClassScanner扫描 Class 文件
BinaryResourceScanner扫描二进制资源文件
GradleScanner扫描Gradle脚本
IssueRegistry: Lint 规则加载的入口,提供要检查的 Issue 列表。

 

3.4 规则实现

 
下面就让我们开始定义XML布局文件内控件id命名检测规则 ViewIdDetector 吧:

  1. 定义 ViewIdDetector 类继承 Detector ,因为我们需要检查的内容在XML文件内,所以还需要实现 Detector.XmlScanner 接口;
  2. 指定文件选择
    在通过 XmlScanner 和 Scope 限制检查范围后,我们还需要使用 appliesTo 对文件进行进一步地选择,这里我限制只有 ResourceFolderType.LAYOUT 类型的布局文件才会进入最终的检测;
    /*** 自定义检测范围,layout文件* @param folderType* @return */@Overridepublic boolean appliesTo(@NonNull ResourceFolderType folderType) {return folderType == ResourceFolderType.LAYOUT;}
  1. 获取元素,进行元素内容分析
  • 重写 getApplicableElements 方法,返回我们所需的空间类型集合
  • 重写 visitElement 方法,获取元素 id 属性,与我们所需的命名规则进行匹配;若不满足规则,则通过 report 方法进行上报;

不同的 Scanner 扫描器所对应的 report 方法各有不同,需要根据具体场景实现;
在这里 report有三个参数,第一个参数是Issue,就是第二步中我们定义的规则; 第二个参数是当前节点; 第三个参数location会返回当前的位置信息,便于在报告中显示定位;最后的字符串用来为警告添加解释。
完整类实现如下:

public class ViewIdDetector extends Detector implements Detector.XmlScanner {/*** "ViewIdCheck" 是 Lint 规则的 id,必须是唯一的。* "ViewId命名不规范" 是简述。* "ViewIdName建议使用 view的缩写加上_xxx,例如tv_xxx, iv_xxx" 是详细解释。* 5 是优先级系数。必须是1到10之间的某个值。* ERROR 是严重程度* Implementation 是Detector间的桥梁,用于发现问题。Scope则用于确认分析范围。在本例中,我们必须处于资源文件层面才能分析前缀问题。*/public static Issue ISSUE = Issue.create("ViewIdCheck","ViewId命名不规范","ViewIdName建议使用 view的缩写加上_xxx,例如tv_xxx, iv_xxx",Category.CORRECTNESS,5,Severity.ERROR,new Implementation(ViewIdDetector.class, Scope.RESOURCE_FILE_SCOPE));/*** 自定义检测范围,layout文件* @param folderType* @return*/@Overridepublic boolean appliesTo(@NonNull ResourceFolderType folderType) {return folderType == ResourceFolderType.LAYOUT;}@Nullable@Overridepublic Collection<String> getApplicableElements() {return Arrays.asList(SdkConstants.TEXT_VIEW, SdkConstants.IMAGE_VIEW, SdkConstants.BUTTON);}@Overridepublic void visitElement(@NotNull XmlContext context, @NotNull Element element) {//判断是否设置了 idif (!element.hasAttributeNS(SdkConstants.ANDROID_URI, SdkConstants.ATTR_ID)) {return;}//获取 id 命名,并进行前缀校验Attr attr = element.getAttributeNodeNS(SdkConstants.ANDROID_URI, SdkConstants.ATTR_ID);String value = attr.getValue();if (value.startsWith(SdkConstants.NEW_ID_PREFIX)) {String idValue = value.substring(SdkConstants.NEW_ID_PREFIX.length());boolean matchRule = true;String expMsg;switch (element.getTagName()) {case SdkConstants.TEXT_VIEW:expMsg = "tv_";matchRule = idValue.startsWith(expMsg);break;case SdkConstants.IMAGE_VIEW:expMsg = "iv_";matchRule = idValue.startsWith(expMsg);break;case SdkConstants.BUTTON:expMsg = "btn_";matchRule = idValue.startsWith(expMsg);break;}if (!matchRule) {context.report(ISSUE, attr, context.getLocation(attr), "ViewIdName建议使用view的缩写_xxx; ${element.tagName} 建议使用 `${expMsg}_xxx`");}}}
}
  1. 创建一个 Issue 对象(即一条规则),用于和 ViewIdDetector 进行绑定;
public static Issue ISSUE = Issue.create("ViewIdCheck","ViewId命名不规范","ViewIdName建议使用 view的缩写加上_xxx,例如tv_xxx, iv_xxx",CustomizeCategory.NAMING_CONVENTION,5,Severity.ERROR,new Implementation(ViewIdDetector.class, Scope.RESOURCE_FILE_SCOPE));

Issue.create方法说明如下

fun create(id: String,briefDescription: String,explanation: String,category: Category,priority: Int,severity: Severity,implementation: Implementation
)

id:唯一的id,简要表面当前提示的问题;
briefDescription: 简单描述当前问题参数;
explanation:详细解释当前问题和修复建议;
category:问题类别,可自定义;
priority:从1到10,10最重要;
Severity:严重程度,FATAL(奔溃), ERROR(错误), WARNING(警告),INFORMATIONAL(信息性),IGNORE(可忽略);
Implementation:Issue和哪个Detector绑定,以及声明 Scope 检查的范围

Category 也可以自定义类型,如下:

public class CustomizeCategory {public static final Category NAMING_CONVENTION = Category.create("CustomizeCategory命名规范", 100);
}
  1. 规则注册
    定义 CustomizeIssueRegistry 继承 IssueRegistry 提供 Issue 集合
class CustomizeIssueRegistry : IssueRegistry() {override val issues: List<Issue>get() = mutableListOf(ViewIdDetector.ISSUE)
}

gradle 中注册 CustomizeIssueRegistry

jar {manifest {attributes("Lint-Registry-v2": "com.jiang.lint.customize.CustomizeIssueRegistry")}
}

至此,规则定义部分已经完成。

  1. 使用
  • 通过 lintChecks 直接将 Lint module 引入需要检测module后,使用 Lint 命令即可进行检测。
lintChecks project(':lint_customize')

我们可以在代码中直接看到自定义规则的提示:
在这里插入图片描述
也可以在检测报告中找到规则的检测结果:
在这里插入图片描述

  • 为了更方便快捷的集成自定义的规则,我们还可以将其打包为 Jar 然后放入一个 aar 中,以便快速依赖;

4、自定义规则适用场景

 

我们选择了自定义规则,必是要有用武之地,让我们来看看哪些场景下可以发挥自己的创造力:
 
1. 编码规则
如上面的例子中,我们就要求开发人员在XML中进行 id 命名时必须带上控件缩写前缀,同理我们还可以检测文件命名、文件大小等;
 
2. 统一工具库使用
在项目开发中,我们往往会用到很多基础的工具功能,其中大部分我们会沉淀为一个底层工具库,在工具库中会针对各种异常场景进行完善的处理,但并不是所有开发者都知道这个工具库的存在,这里我们就看定义规则进行检测。
如:Log日志、Toast、SharedPrefrence、Glide、Picasso 等,我们可以定义规则检查 Java/Kotlin 文件,限制必须使用统一工具库提供接口;
 
3. TODO Check
检查代码中是否还有TODO尚未完成。例如开发时可能会在代码中写一些测试数据,最终上线前要确保删除;
 
4. 个人隐私限制
现在对于用户个人隐私安全越来越重视,很多系统API因涉及隐私政策问题,需要严格控制其使用。在此我们就可以指定规则检测特定API调用,来限制隐私数据的获取;

 
以上为几个条为本人认为可以继续实践的场景,大家可以发散思维寻找更多有价值的实现!

结语

 
本文对于Lint的自定义选择了一个小场景进行了实现,但是在项目开发中我们可能会对于Java、Kotlin、字节码等进行规则的定义,因覆盖检测范围的不同需要实现不同的 Lint API。大家在实际尝试中可以多多借鉴 lint-checks 库内已提供的 Detector,帮助理解更多API的正确使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/315447.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.2 作业

spi.h #ifndef __SPI_H__ #define __SPI_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" // MOSI对应的引脚输出高低电平的信号PE14 #define MOSI_OUTPUT_H() do{GPIOE->ODR | (0x1 << 14);}while(0) #define MOSI_OUTPUT_L…

嵌入式ARM作业5

作业要求&#xff1a;实现数码管不同位显示不同的数字 spi.c #include "spi.h"void delay_us1(unsigned int us) {int i,j;for(i 0; i < us;i)for (j 0; j < 1;j); }void SPI_init(void) {RCC->MP_AHB4ENSETR | (0x1 << 4);// MOSI PE14 GPIOE…

SpringIOC之support模块ConversionServiceFactoryBean

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

揭秘计算机内部通信:探秘数据、地址与控制信号的奥秘

引言 在我们前面的讲解中&#xff0c;我们详细了解了计算机系统的核心组件&#xff0c;包括CPU、内存和磁盘。然而&#xff0c;总线在这个体系中同样至关重要。总线是计算机内部各部件间通信的桥梁&#xff0c;涉及数据、地址和控制信号的传输。在接下来的内容中&#xff0c;我…

JMeter 接口测试,一文详细讲解如何使用

1、获取 API 信息 API的方法&#xff1a;GET API的URL&#xff1a; https://api.seniverse.com/v3/weather/now.json API的参数&#xff1a; 请求参数&#xff1a; language 参数值范围&#xff1a; zh-Hans 简体中文 zh-Hant 繁体中文 en 英文 ja 日语 de 德语 fr …

使用网站,如何保护信息和数据不会被泄露?

在生活中&#xff0c;网站的作用越来越重要。 一方面网站是获取各种信息最常用的途径之一&#xff0c;它们可以提供实时的新闻、评论、分析、数据、指南等&#xff0c;让人们更轻松地了解世界和获取所需的知识。 同时是现代商业的关键组成部分之一&#xff0c;它们可以为企业…

将Qt窗口停靠在Maya界面中

问题描述&#xff1a; 将PySide2/PyQt工具的窗口停靠在Maya的界面中 解决方法&#xff1a; from PySide2 import QtCore, QtGui, QtWidgetsfrom maya.app.general.mayaMixin import MayaQWidgetDockableMixinclass MainWindow(MayaQWidgetDockableMixin, QtWidgets.QMainWind…

ChatGPT 进行 SEO的使用技巧

搜索引擎优化 (SEO) 是使网站对搜索引擎友好的一种不断发展的实践。 自搜索引擎和新兴技术的发展以来&#xff0c;它从未保持不变。 最近发布的 ChatGPT 是一种人工智能对话工具&#xff0c;似乎在搜索引擎优化方面有很好的应用。 从创建吸引人的标题到只需一个简短的提示就可…

普中STM32-PZ6806L开发板(HAL库函数实现-USART2 中断接收)

简介 实现USART2 的 中断接收&#xff0c; 发送数据。电路原理图 USART2接线 原理图USART2 在主芯片引脚 实物图 其他知识 APIs stm32f1xx_hal_uart.h /* 堵塞发送, pData是发送数据, Size发送数据大小, Timeout是超时时间 */ HAL_StatusTypeDef HAL_UART_Transmit(UAR…

Head First Design Patterns - 装饰者模式

什么是装饰者模式 装饰者模式动态地将额外责任附加到对象上。对于拓展功能&#xff0c;装饰者提供子类化的弹性替代方案。 --《Head First Design Patterns》中的定义 为什么会有装饰者模式 根据上述定义&#xff0c;简单来说&#xff0c;装饰者模式就是对原有的类&#xff0c…

MySql篇——MySql使用常见问题及解决办法

这里汇总MySql使用常见问题及解决办法&#xff0c;会持续更新。 问题1.ERROR 1819 (HY000): Your password does not satisfy the current policy requirements。 含义&#xff1a;你设置的密码不符合当前的密码等级。 可使用 SHOW VARIABLES LIKE validate_password%; 查看…

20个Laravel教程资源助你快速入门和进阶

Laravel多年来一直是PHP应用程序开发的摇滚明星&#xff0c;这是有充分理由的。庞大的生态系统、活跃的社区、强大的就业市场、成功的初创公司——它拥有一切让采用新技术变得值得的东西。 如果你想学习Laravel&#xff0c;你不需要更进一步。通过浏览本指南&#xff0c;您可以…