Android 自定義 lint 插件(一)

本文已同步發(fā)表到:https://glorin.xyz/2020/10/31/android_custom_lint/

問題

Android lint 是一個靜態(tài)代碼掃描工具,sdk 本身已經(jīng)支持了一些檢查規(guī)則,但是有時候我們需要根據(jù)自己的業(yè)務(wù)或者代碼規(guī)范自定義一些檢查規(guī)則,這時候就需要用到自定義 lint 的功能,本文介紹如何自定義一個 lint 檢查規(guī)則。

環(huán)境

MacOS Catalina + Android Studio 4.1 + Gralde 6.5

工程結(jié)構(gòu)

首先創(chuàng)建一個 Android 工程,我將它命名為 CustomLintChecker,默認帶有一個 app 模塊,我們另外創(chuàng)建兩個模塊:

  1. checker 模塊,是一個 Java Library
  2. wrapper 模塊,是一個 Android Library

最終工程結(jié)構(gòu)如下:

CustomLintChecker
+ app
+ wrapper
+ checker

Checker 模塊

Gradle 配置

Checker 模塊是 lint 的檢查邏輯,首先我們在 build.gradle 中引入 lint-api 及 com.android.lint 插件:

plugins {
    id 'java-library'
    id 'kotlin'
    id 'com.android.lint'
}

...

dependencies {
    compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

    def lint_version = '27.1.0'
    compileOnly "com.android.tools.lint:lint-api:$lint_version"
    compileOnly "com.android.tools.lint:lint-checks:$lint_version"
}

值得注意的是這邊的依賴全部用 compileOnly,否則可能會導致依賴沖突。

Detector

配置完 gradle 之后,我們便著手編寫我們的 lint 檢查規(guī)則,此處以一個 log 檢查為示例,我們的目標是檢測到代碼中使用了 android.util.Log 時,提示用戶修改為 Timber

Detector 代碼如下,相關(guān)代碼作用見注釋

// 集成 Detector類,并實現(xiàn) SourceScanner 接口
class AndroidLogDetector : Detector(), SourceCodeScanner {
    // 獲取感興趣的方法名,可以理解為讓 lint 規(guī)則應(yīng)用于返回的方法名列表,因為我們不可能檢測每一個方法,所以此處要限定范圍。
    override fun getApplicableMethodNames(): List<String>? {
        return listOf("wtf", "v", "d", "i", "w", "e")
    }

    // visitMethodCall 回調(diào),當 lint 運行到上面的方法調(diào)用時,便會調(diào)用這個方法,我們在這里處理檢測邏輯
    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        // 判斷方法是否是 android.util.Log 的方法
        if (context.evaluator.isMemberInClass(method, "android.util.Log")) {
            // 報告問題,第一個參數(shù) ISSUE 是問題的數(shù)據(jù),在后面定義,第二個參數(shù)是要報告的位置,可以理解為代碼的位置,后面是一個 Message
            context.report(ISSUE, context.getLocation(node), "Don't use Android log directly")
        }
    }

    companion object {
        // 此處便是上面用到的 ISSUE,很容易理解相關(guān)參數(shù)的含義,此處不再贅述
        val ISSUE = Issue.create(
            "AndroidLogDeprecated",
            "Please don't use android log directly, use Timber instead",
            "Please don't use android log directly, use Timber instead",
            Category.LINT,
            5,
            Severity.WARNING,
            Implementation(AndroidLogDetector::class.java, Scope.JAVA_FILE_SCOPE)
        )
    }
}

Registry

編寫完 Detector 后,我們要對規(guī)則進行注冊,這樣 IDE 或者 gradle lint,才能加載我們的 Detector。此處我們需要一個 Registry 類,代碼如下:

package xyz.glorin.customlint.checker

import com.android.tools.lint.client.api.IssueRegistry
import xyz.glorin.customlint.checker.detectors.AndroidLogDetector

class CustomRegistry : IssueRegistry() {
    override val issues = listOf(
        AndroidLogDetector.ISSUE
    )
}

這個類非常簡單,繼承了 IssueRegistry,并修改了 issues 字段,很容易理解這是在注冊這個 ISSUE。

META-INF 配置

為了完成注冊,我們還需要配置一個 IssueRegistry 文件,這樣 lint 框架才能讀取到我們的 Registry,這個類的路徑是: checker/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry,文件內(nèi)容頁很簡單:

xyz.glorin.customlint.checker.CustomRegistry

其實就是咱們的 Registry 類的完整類名。
如此我們的 checker 模塊算是完成了。

Wrapper

checker 模塊是一個 Java 模塊,編譯完成后,是一個 jar,傳統(tǒng)的應(yīng)用 lint 規(guī)則的方式是,把這個 jar 放在 ~/.android/lint 目錄下,但是這樣十分不方便維護,且會應(yīng)用到所有項目中,于是我們需要尋求其他方式。

好在 gradle 支持讀取 aar 中的 lint.jar 并應(yīng)用相應(yīng)規(guī)則,且在新版 gradle 中新增了 lintPublish 方法支持更快捷地打包 lint 檢測規(guī)則,我們要做的很簡單,就是修改 wrapper 模塊的 build.gradle 文件,內(nèi)容如下:

...
dependencies {
    lintPublish project(':checker')
}

由于這只是一個簡單的 wrapper 項目,因此可以考慮將 dependencies 其他內(nèi)容都刪除。

App

wrapper 模塊也編寫完成,其實 lint 檢測規(guī)則的工程已經(jīng)配置完成了,我們接下來就要試試效果,在 app 模塊的 build.gradle 文件中,依賴 wrapper 模塊:

implementation project(':wrapper')

然后在 MainActivity.kt 中,寫下 Log 代碼,此時 lint 不一定即時生效,可以在 terminal 中,運行一下命令:

./gradlew :app:lint

運行完成后,會輸出 lint 檢查報告,不出意外地話,IDE 也會出現(xiàn)相應(yīng)的提示了。

代碼

本文工程代碼: https://github.com/glorinli/CustomLintChecker

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容