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

news2024/11/24 20:05:25

概述


在日常的项目开发迭代中,相信每个人对与代码质量都是有着高要求的。但是,在所有事情中,人往往就是其中最大的变量因素,个人各异,如何去保障代码质量以及统一规范呢?开发团队也许会严格要求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 
     */
    @Override
    public 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
     */
    @Override
    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
        return folderType == ResourceFolderType.LAYOUT;
    }

    @Nullable
    @Override
    public Collection<String> getApplicableElements() {
        return Arrays.asList(SdkConstants.TEXT_VIEW, SdkConstants.IMAGE_VIEW, SdkConstants.BUTTON);
    }

    @Override
    public void visitElement(@NotNull XmlContext context, @NotNull Element element) {
        //判断是否设置了 id
        if (!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.coloradmin.cn/o/1350331.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用Node.js快速创建本地HTTP服务器并实现公网访问服务端

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

抖店新手该如何运营?

我是电商珠珠 在抖店开好之后&#xff0c;大部分新手都不知道怎么去运营&#xff0c;今天&#xff0c;我就来给大家详细的讲一下。 第一步&#xff0c;店铺基础设置 我一直跟我的学生讲&#xff0c;一定要懂基本流程&#xff0c;只有前期将流程跑通了后期才可以毫无压力。 …

css 保持元素宽高比 随页面宽度变化高度自适应

目录 1.效果展示 2.代码内容 3.代码解析 &#xff08;1&#xff09;分析inner类写法 &#xff08;2&#xff09;分析container类写法 1.效果展示 2.代码内容 <div class"item"><div class"inner"><di class"container">…

LED显示屏恒流驱动芯片的功能特点

LED显示屏恒流驱动芯片是一种电子器件&#xff0c;用于控制和驱动LED显示屏。由于LED显示屏通常由大量LED灯珠组成&#xff0c;因此需要使用恒流驱动芯片来确保每个LED灯珠都能够正常、均匀地发光&#xff0c;并且能够控制显示的色彩和亮度。 SM16380SF 恒流驱动芯片通常具有以…

Windows本地如何部署Apache服务器搭配内网穿透实现无公网IP远程访问?

文章目录 前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web ui管理界面3.2 创建公网地址 4. 固定公网地址 前言 Apache作为全球使用较高的Web服务器…

RDS创建数据库

目录 创建数据库 创建账号与授权 连接RDS数据库 创建数据库 在创建数据库的页面&#xff0c;你需要设置数据库的名称、字符集、排序规则等信息。 字符集&#xff1a;字符集&#xff08;Character set&#xff09;是多个字符的集合&#xff0c;字符集种类较多&#xff0c;每个…

前缀和算法 -- [模版]二维前缀和

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 本题链接 【模板】二维前缀和_牛客题霸_牛客网 输入描述 n是行&#xff0c;m是列&#xff0c;q是查询次数&#xff0c;x1&#xff0c;y1&#xff0c;x2&#xff0c;y2是二维数组的下标。 输出描述 通过两对下标&#x…

nginx+keepalived实现七层负载

目录 一、部署nginx01、nginx02 二、keepalived配置&#xff08;抢占模式、master- backup模式&#xff09; 三、测试 四、非抢占模式&#xff08;backup-backup模式&#xff09; nginx01 11.0.1.31nginx0211.0.1.32虚拟IP&#xff08;VIP&#xff09;11.0.1.30 一、部署ngin…

JDBC->SpringJDBC->Mybatis封装JDBC

一、JDBC介绍 Java数据库连接&#xff0c;&#xff08;Java Database Connectivity&#xff0c;简称JDBC&#xff09;是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口&#xff0c;提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们…

WPF+Halcon 培训项目实战(8-9):WPF+Halcon初次开发

文章目录 前言相关链接项目专栏运行环境匹配图片WPF Halcon组件HSmartWindowControlWPF绑定读取图片运行代码运行结果 抖动问题解决运行结果 绘制矩形绘制图像会消失 绘制对象绑定事件拖动事件 前言 为了更好地去学习WPFHalcon&#xff0c;我决定去报个班学一下。原因无非是想…

数据结构——顺序栈与链式栈的实现

目录 一、概念 1、栈的定义 2、栈顶 3、栈底 二、接口 1、可写接口 1&#xff09;数据入栈 2&#xff09;数据出栈 3&#xff09;清空栈 2、只读接口 1&#xff09;获取栈顶数据 2&#xff09;获取栈元素个数 3&#xff09;栈的判空 三、栈的基本运算 四、顺序栈&…

【CISSP学习笔记】5. 安全架构和工程

该知识领域涉及如下考点&#xff0c;具体内容分布于如下各个子章节&#xff1a; 使用安全设计原理来研究、实施与管理工程过程理解安全模型的基本概念&#xff08;例如 Biba、Star Model、Bell-LaPadula 等模型&#xff09;基于系统安全要求选择控制措施理解信息系统 (IS) 的安…

智能对话意图分析服务接口

机器人聊天&#xff0c;智能助手&#xff0c;内容生成&#xff0c;智能办公&#xff0c;智能辅助&#xff0c;智能搜索 一、接口介绍 通过接收用户提出的问题、输入的图片和文档等需求&#xff0c;准确识别其对话意图&#xff0c;并触发相应的回复。同时&#xff0c;整合了AP…

ansible管理windows测试

一、环境介绍 Ansible管理主机&#xff1a; 系统: redhat7.6 Linux管理服务器需安装pywinrm插件 Windows客户端主机&#xff1a; 系统: Server2012R2 Windows机器需要安装或升级powershell4.0以上版本&#xff0c;Server2008R2默认的版本是2.0&#xff0c;因此必须升…

m1芯片电脑上的paragon15如何安装激活 m1芯片电脑上ntfs for mac如何安装

Paragon NTFS软件在M1芯片电脑上安装之后&#xff0c;最后一步会让我们“允许加载第三方内核扩展”&#xff0c;具体如下图所示。 图1&#xff1a;允许加载第三方内核扩展 按照图中提示“单击此处“&#xff0c;然后打开安全与隐私。接下来依次点击小锁标志进行解锁&#xff0c…

【零基础入门TypeScript】TypeScript - 概述

目录 什么是 TypeScript&#xff1f; TypeScript 的特点 TypeScript 和 ECMAScript 为什么使用 TypeScript&#xff1f; TypeScript 的组件 声明文件 JavaScript 是作为客户端语言引入的。Node.js 的发展也标志着 JavaScript 成为一种新兴的服务器端技术。然而&#xff0…

云原生十二问

一、什么是云原生&#xff1f; 云原生是在云计算环境中构建、部署和管理现代应用程序的软件方法。现代企业希望构建高度可扩展、灵活且具有弹性的应用程序&#xff0c;可以快速更新以满足客户需求。为此&#xff0c;他们使用现代工具和技术&#xff0c;这些工具和技术本质上支…

python 深度学习 记录遇到的报错问题11

本篇继python 深度学习 记录遇到的报错问题10-CSDN博客 六、ValueError: cannot convert float NaN to integer 报错&#xff1a; 原因&#xff1a;这个错误通常是因为在尝试将NaN值转换为整数时发生的。NaN表示“非数字”&#xff0c;它无法转换为整数。在 Python 中&#xf…

2023年度回顾:怿星科技的转型与创新

岁月不居&#xff0c;时节如流。随着2023年的落幕&#xff0c;怿星科技在这一年中不仅实现了自身的转型&#xff0c;还在技术创新、产品研发、行业合作和人才培养等方面取得了显著的成就。这一年&#xff0c;怿星科技正式完成了从服务型公司向产品型公司的战略转变&#xff0c;…

媒体捕捉-拍照

引言 在项目开发中&#xff0c;从媒体库中选择图片或使用相机拍摄图片是一个极为普遍的需求。通常&#xff0c;我们使用UIImagePickerController来实现单张图片选择或启动相机拍照。整个拍照过程由UIImagePickerController内部实现&#xff0c;无需我们关心细节&#xff0c;只…