Android开发之lint代码检测编写和使用方法

news2024/11/27 22:43:39

文章目录

  • 前言
    • 项目结构
    • 待检测项目
    • lint检查器
  • 依赖方式
    • 待检测项目
    • lint检查器
  • 代码编写
    • 方法调用检测
      • Detector注册
      • Detector编写
    • Toast硬编码检测
      • Detector注册
      • Detector编写
  • 使用
    • 壳工程打包
    • 待检测项目使用
      • kotlin代码
      • java代码
    • AS识别结果
      • kotlin代码
      • java代码
    • 命令行检测
  • 参考文献

前言

lint是Android自带的静态代码检测框架,用来在不运行代码的情况下,对项目代码进行检测,检测范围包括方法调用、硬编码等。本文从项目结构、依赖、检查器编写、打包、运行测试的环节,描述一个lint检查器的编写和使用全过程。

项目结构

lint检查器首先是一个插件,需要打成aar或jar来被待检测项目使用。其次,lint检查器本身,需要包含两层:外层是壳工程,生成检查器aar供待检测项目使用;aar内容来自内层实现层;内层是实现层,负责检查器逻辑实现、注册等。

为了方便,本文将待检测项目、壳工程和实现层都放在一个项目中,作为不同的模块存在。不过壳工程还是会生成独立的aar,来供待检测模块使用。

待检测项目

在这里插入图片描述
libs中的lintlibout-debug.aar就是壳工程生成的aar,src中有业务代码。

lint检查器

在这里插入图片描述
lintlibout是生成aar的壳工程,里面除了在build.gradle中指定aar的生成方式,没有其他代码;lintlib是实现层,里面存在着检查器相关代码

依赖方式

待检测项目

只需要在build.gradle中依赖到lint检查器aar即可:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.example.testlint'
    compileSdk 32

    defaultConfig {
        applicationId "com.example.testlint"
        minSdk 26
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    implementation files("libs/lintlibout-debug.aar")
}

添加的唯一一行,是dependencies块中最后的implementation files("libs/lintlibout-debug.aar")

lint检查器

壳工程的依赖方式如下,在dependencies模块中指定lint aar的源工程:

plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'comszc.lintlibout'
    compileSdk 32

    defaultConfig {
        minSdk 26
        targetSdk 32

        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    lintPublish project(':lintlib')
}

实现层需要:

  1. 依赖lint组件,注意lint版本,版本不同可能检测代码写法不一样;
  2. 指定将源文件生成jar;
  3. 指定lint注册器,用来向lint内核注册自定义的issue
plugins {
    id 'org.jetbrains.kotlin.jvm'
    id 'java-library'
}

dependencies {
    // 1. 依赖Android的lint组件
    compileOnly "com.android.tools.lint:lint-api:30.2.1"
    compileOnly "com.android.tools.lint:lint-checks:30.2.1"
    compileOnly "com.android.tools.lint:lint:30.2.1"
    compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20"
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

jar {
    manifest {
        // 2. 指定lint注册器
        attributes('Lint-Registry-V2': 'com.szc.lintlib.MyIssueRegistry')
    }
}

// 3. 将源文件生成jar,同时调用第2步注册我们的注册器
configurations {
    lintJarOutput
}
dependencies {
    lintJarOutput files(jar)
}

代码编写

检测器代码编写可分为两大步:Detector注册和Detector编写,均在liblint实现层中。本文通过方法调用检测和Toast硬编码检测两个案例,来进行说明。

方法调用检测

Detector注册

编写IssueRegistry的子类,覆写getIssues()方法,返回要检测的检测issue

public class MyIssueRegistry extends IssueRegistry {
    @NotNull
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(
                MyDetector.ISSUE 
        );
    }
}

MyDetector是检测器,ISSUE在其中定义,所以直接看下一小节的Detector编写即可。

Detector编写

public class MyDetector extends Detector implements Detector.UastScanner {
    private static final String ISSUE_ID = "LogUseError";
    private static final String ISSUE_DESCRIPTION = "Please use LogUtil instead of Log.";
    private static final String ISSUE_EXPLANATION = "Please use LogUtil instead of Log, because I don`t like it!";
    private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;
    private static final int ISSUE_PRIORITY = 5;
    private static final Severity ISSUE_SEVERITY = Severity.WARNING;

    // 1、定义 ISSUE
    public static final Issue ISSUE = Issue.create(
            ISSUE_ID,               // 唯一ID 这个id必须是独一无二的
            ISSUE_DESCRIPTION,      // 简单描述
            ISSUE_EXPLANATION,      // 详细描述
            ISSUE_CATEGORY,         // 验证正确性
            ISSUE_PRIORITY,         // 权重,优先级,必须在1到10之间。
            ISSUE_SEVERITY,         // 这是一个警告
            new Implementation(     // 这是连接Detector与Scope的桥梁,其中Detector的功能是寻找issue,而scope定义了在什么范围内查找issue
                    MyDetector.class,
                    Scope.JAVA_FILE_SCOPE));
    private static final List<String> CANDIDATE_METHOD_NAMES = Arrays.asList(
            "i", "d", "e", "v", "w", "wtf"
    );
    private static final String LOG_CLASS = "android.util.Log";

    // 2. 定义要检查的方法
    @Override
    public List<String> getApplicableMethodNames() {
        return CANDIDATE_METHOD_NAMES;
    }

    @Override
    public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
        if (!context.getEvaluator().isMemberInClass(method, LOG_CLASS)) {
            // 3. 判断是不是指定的目标类
            return;
        }

        // 4. 上报issue信息
        context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION);
    }
}

Toast硬编码检测

Detector注册

IssueRegistry的子类的getIssues()方法添加新的检测issue

public class MyIssueRegistry extends IssueRegistry {
    @NotNull
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(
                MyDetector.ISSUE,
                ToastHardCodeDetector.ISSUE_HARD_CODE 
        );
    }
}

Detector编写

public class ToastHardCodeDetector extends Detector implements Detector.UastScanner {
    private static final String ISSUE_ID_HARD_CODE = "HardCodeToast";
    private static final String ISSUE_DESCRIPTION_HARD_CODE = "Toast should not be hard-coded";
    private static final String ISSUE_EXPLANATION_HARD_CODE = "Using hard-coded Toast message is not recommended, " +
            "as it makes it harder to maintain, translate and update the app.";
    private static final Category ISSUE_CATEGORY_HARD_CODE = Category.CORRECTNESS;
    private static final int ISSUE_PRIORITY_HARD_CODE = 5;
    private static final Severity ISSUE_SEVERITY_HARD_CODE = Severity.WARNING;

    private static final String TOAST_CLASS = "android.widget.Toast";
    private static final String MAKE_TEXT_METHOD = "makeText";

    // 1. 定义Issue
    public static final Issue ISSUE_HARD_CODE = Issue.create(
            ISSUE_ID_HARD_CODE, // Issue的Id
            ISSUE_DESCRIPTION_HARD_CODE, // 简单描述
            ISSUE_EXPLANATION_HARD_CODE, // 详细描述
            ISSUE_CATEGORY_HARD_CODE, // 检查类型:检查正确性
            ISSUE_PRIORITY_HARD_CODE, // 检查优先级
            ISSUE_SEVERITY_HARD_CODE, // 检查不通过时的严重性
            new Implementation(ToastHardCodeDetector.class, Scope.JAVA_FILE_SCOPE)); // 连接Detector与Scope的桥梁,其中Detector的功能是寻找issue,而scope定义了在什么范围内查找issue,这里是在java或kotlin源文件中寻找并检测issue。

    // 2. 定义要检测的方法
    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList(MAKE_TEXT_METHOD);
    }

    // 3. 方法检测的具体逻辑
    @Override
    public void visitMethodCall(@NotNull JavaContext context, @Nullable UCallExpression call, @NotNull PsiMethod method) {
        if (!context.getEvaluator().isMemberInClass(method, TOAST_CLASS)) {
            // 该方法是否在目标类中实现
            return;
        }

        if (call == null) {
            return;
        }


        // 获取目标方法的入参列表
        List<UExpression> arguments = call.getValueArguments();

        if (arguments == null || arguments.size() < 3) {
            return;
        }

        // 获取某个入参,Toast.makeText()方法中第2个参数容易被硬编码,是检测目标
        UExpression messageArgument = arguments.get(1);

        if (messageArgument == null) {
            return;
        }

        // 获取该入参的原始字符串,是字符串的话会加上""
        String sourceMessage = messageArgument.asSourceString();

        if (sourceMessage == null) {
            return;
        }

        if (sourceMessage.matches("\".*\"")) {
            // 检测到message参数硬编码,则上报issue
            context.report(ISSUE_HARD_CODE, messageArgument, context.getLocation(messageArgument),
                    ISSUE_DESCRIPTION_HARD_CODE);
        }
    }
}

使用

壳工程打包

在这里插入图片描述
点击后,会在壳工程lintlibout/build/outputs/aar目录下生成检测的aar包:
在这里插入图片描述

待检测项目使用

根据2.1节指定的依赖方式,将生成的aar复制到libs目录下:
在这里插入图片描述
编写待检测的代码:

kotlin代码

class MainActivity: Activity() {
    fun test() {
        // 检测Log.*方法
        Log.i("", "")
        Log.d("", "")

        // 检测Toast硬编码
        Toast.makeText(this, "", Toast.LENGTH_LONG).show()

        val text = "text"
        Toast.makeText(this, text, Toast.LENGTH_LONG).show()
        // 检测套娃是否能逃过Toast硬编码检测
        ToastUtils.show(this, "")
    }
}

java代码

public class TestLint {
    void func() {
        // 检测Log.*方法
        Log.i("", "");
    }
}

//
public class ToastUtils {
    public static void show(Context context, String text) {
        // 检测Toast硬编码
        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
    }
}

AS识别结果

可能需要重启下AS才能生效:

kotlin代码

Log.*方法检测:
在这里插入图片描述
Toast硬编码:
在这里插入图片描述

java代码

Log.*方法检测:
在这里插入图片描述
Toast硬编码检测(测试是否会误报):
在这里插入图片描述

命令行检测

在项目根目录下,运行gradlew :app:lint,其中app是待检测的模块:
在这里插入图片描述

D:\develop\Android\AndroidStudioProjects\TestLint> gradlew lint

> Task :app:compileDebugKotlin
'compileDebugJavaWithJavac' task (current target is 11) and 'compileDebugKotlin' task (current target is 1.8) jvm target compatibility should be set to the same Java version.

> Task :app:lintAnalyzeDebug
com.szc.lintlib.MyIssueRegistry in D:\develop\Android\gradle\caches\transforms-3\8140217cb6f1adb1d3c77da9def64777\transformed\lintlibout-debug\jars\lint.jar does not specify a vendor; see IssueRegistry#vendor

> Task :app:lintReportDebug
Wrote HTML report to file:///D:/develop/Android/AndroidStudioProjects/TestLint/app/build/reports/lint-results-debug.htm

BUILD SUCCESSFUL in 31s
58 actionable tasks: 34 executed, 24 up-to-date
D:\develop\Android\AndroidStudioProjects\TestLint>

检测输出在待检测模块的build/reports目录下:
在这里插入图片描述
最清楚的应该是html格式的报告了,我们可以用浏览器打开查看:
在这里插入图片描述

参考文献

官网介绍:https://developer.android.google.cn/studio/write/lint?hl=zh-cn#commandline
其他论坛:https://zhuanlan.zhihu.com/p/307382854

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

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

相关文章

Fushion 360齿轮组制作教程

本教程总结 Kevin小哥的教程&#xff0c;原视频教程点击这里&#xff0c;笔者录制的教程视频见fushion 360齿轮制作 Fushion 360齿轮组制作教程 基本步骤添加齿轮工具相关设置绘制底板创建演示动画掏空较大的齿轮 视频教程及成品展示 基本步骤 添加齿轮工具 选择使用工具&…

【深度学习】 Python 和 NumPy 系列教程(三):Python容器:1、列表(初始化、索引和切片、常用操作、常用函数、拆包、遍历)

目录 一、前言 二、实验环境 三、Python容器 0. 容器介绍 1. 列表 1. 初始化 a. 创建空列表 b. 使用现有元素初始化列表 c. 使用列表生成式 d. 复制列表 2. 索引和切片 a. 索引 b. 负数索引 c. 切片 3. 常用操作&#xff08;更新、删除&#xff09; a. 更新单个…

【漏洞复现】Tenda路由器存在密码泄露

漏洞描述 腾达W15E路由器外置4根增强型360全向天线&#xff0c;苛刻调校到0.01毫米级的零干扰间距&#xff0c;科学独立布局的信号放大器PA&#xff0c;穿墙性能更强劲&#xff0c;覆盖范围更广&#xff0c;在哪都有好信号。 该型号的路由器系统存在密码泄露漏洞&#xff0c;…

【MySQL】聊聊数据库高可用

对于任何一个互联网公司来说&#xff0c;必定涉及到存储系统&#xff0c;而一般主流的使用MySQL进行存储数据&#xff0c;但是如果只是部署一台数据库&#xff0c;数据丢失的话&#xff0c;其实没有办法进行有效的恢复&#xff0c;那么就会造成一定的损失。要么就是直接的损失&…

pc端字体为什么到12像素以后不生效

pc端字体为什么到12像素以后不生效&#xff1f; 因为谷歌浏览器默认的最小字体是12像素&#xff0c;如果去‘设置’里面修改最小字体&#xff0c;是可以达到修改效果的&#xff0c;但是&#xff0c;你不能解决用户的浏览器设置 解决办法如下 <p class"font">…

SG-Former:具有进化Token重新分配的自引导Transformer

摘要 https://arxiv.org/pdf/2308.12216.pdf Vision Transformer在各种视觉任务中展示了令人印象深刻的成功。然而&#xff0c;其沉重的计算成本相对于Token序列长度呈二次增长&#xff0c;在很大程度上限制了其处理大型特征映射的能力。为了降低计算成本&#xff0c;以往的研…

华为云云耀云服务器L实例评测|服务器实例基础使用实践

&#x1f996;我是Sam9029&#xff0c;一个前端 Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-JS学习,CSS学习,Vue-2领域博主 **&#x1f431;‍&#x1f409;&#x1f431;‍&#x1f409;恭喜你&#xff0c;若此文你认为写的不错&#xff0c;不要吝啬你的赞扬&#xff0c;求…

常见的图像格式介绍:RAW、RGB、YUV

常见的图像格式有RAW、RGB、YUV这三大类 1. RAW raw图像指的是sensor输出的原始数据&#xff0c;常见的有8位、10位、12位之分&#xff0c;分别表示一个像素点所占的字节数为8bit、10bit、12bit。 raw数据常见的有四种Bayer模式&#xff1a;GRBG、RGGB、BGGR&#xff08;下图…

苹果计划2024年AirPods引入新接口,后续升级体温测量、听力测试

据彭博社马克・古尔曼&#xff08;Mark Gurman&#xff09;报道&#xff0c;苹果公司计划在最早2024年为其旗舰无线耳机AirPods和AirPods Max引入USB-C端口&#xff0c;并试图将其所有基于Lightning接口的配件改用USB-C接口。 据古尔曼表示&#xff0c;AirPods Pro将率先改用新…

学校ROS培训项目记录

项目内容&#xff1a; 在linux上安装ROS&#xff08;可以使用虚拟机&#xff09;编写ROS程序&#xff0c;实现话题的订阅和发布基于松灵小车硬件&#xff0c;显示雷达数据、图像数据移动松灵小车&#xff0c;绘制小车的运动轨迹 实现过程记录 1.ubuntu22.04上安装ros2 ubun…

Cookie、Session、Token、JWT

文章目录 1. 认证(Authentication) & 授权(Authorization) & 凭证(Credentials)1.1 认证(Authentication)1.2 授权(Authorization)1.3 凭证(Credentials) 2. Cookie & Session2.1 什么是 Cookiecookie 重要的属性 2.2 什么是 Session2.3 Cookie 和 Session 的区别 …

来看看Python编码规范(PEP 8)

在讲解具体的​ Python 编 ​码规范之前&#xff0c;先来看看图 1 中的代码&#xff1a; 图 1 两段功能相同的 Python 代码 对比图 1 中的两段代码你会发现&#xff0c;它们所包含的代码时完全相同的&#xff0c;但很明显&#xff0c;右侧的代码编写格式看上去比左侧的代码段更…

VR桥梁应用:实现可视化的桥梁监控

在过去的几年中&#xff0c;虚拟现实(VR)技术已经在多个领域得到了广泛的应用。其中&#xff0c;桥梁巡检是一个特别有前景的应用领域。利用VR技术&#xff0c;我们可以创建一个安全、高效的桥梁巡检方式&#xff0c;帮助工程师和维护人员更好地理解和评估桥梁的状态。 通过VR设…

Mac电脑使用万能头文件教程(详细)

参考:https://blog.csdn.net/weixin_46522531/article/details/126292477 预计阅读操作时间&#xff1a;5分钟 Mac电脑由于使用的是苹果自己的编译器&#xff0c;很多头文件不支持&#xff0c;像万能头就是其中的一员。万能头可以为我们节省很多时间&#xff0c;减少很多不必要…

企业架构LNMP学习笔记28

企业架构LNMP高可用负载均衡服务器之Nginx&#xff1a; 1&#xff09;能够描述负载均衡的作用&#xff1b;loadbalance LB。 2&#xff09;能够了解负载均衡常见的实现方式&#xff1b; 3&#xff09;能够使用nginx实现负载均衡&#xff1b; 4&#xff09;能够描述nginx的常…

OpenCV DNN神经网络部署流程

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 目录 前言 一、加载网络模型 二、构建输入 三、执行推理 四、解析输出 前言…

公开游戏、基于有向图的游戏

目录 〇&#xff0c;背景 一&#xff0c;公开游戏、策梅洛定理 1&#xff0c;公开游戏 2&#xff0c;策梅洛定理 二&#xff0c;有向图游戏 1&#xff0c;狭义有向图游戏 2&#xff0c;广义有向图游戏 3&#xff0c;狭义有向图游戏的SG数 4&#xff0c;Bash Game 力扣…

【List篇】使用Arrays.asList生成的List集合,操作add方法报错

早上到公司&#xff0c;刚到工位&#xff0c;测试同事就跑来说"功能不行了&#xff0c;报服务器异常了&#xff0c;咋回事";我一脸蒙&#xff0c;早饭都顾不上吃&#xff0c;要来了测试账号复现了一下&#xff0c;然后仔细观察测试服务器日志&#xff0c;发现报了一个…

【C语言】错题本(3)

题目: 为了方便分析,我们将题目代码贴在下面: int main() {unsigned char puc[4];struct tagPIM{unsigned char ucPim1;unsigned char ucData0 : 1;unsigned char ucData1 : 2;unsigned char ucData2 : 3;}*pstPimData;pstPimData (struct tagPIM*)puc;memset(puc,0,4);pstPi…

【计算机网络】HTTP(上)

文章目录 1.HTTP概念2. URLurlencode 和 urldecode转义规则 3. HTTP的宏观理解HTTP的请求HTTP的响应 4. 见一见HTTP请求和响应请求报头 1. 模拟一个简单的响应response响应报头 2. 从路径中获取内容ReadFile函数的实现 3.不同资源进行区分反序列化的实现ReadOneLine函数的实现P…