Android 自定義 lint 插件(一)

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

問(wèn)題

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

環(huán)境

MacOS Catalina + Android Studio 4.1 + Gralde 6.5

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

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

  1. checker 模塊,是一個(gè) Java Library
  2. wrapper 模塊,是一個(gè) Android Library

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

CustomLintChecker
+ app
+ wrapper
+ checker

Checker 模塊

Gradle 配置

Checker 模塊是 lint 的檢查邏輯,首先我們?cè)?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,否則可能會(huì)導(dǎo)致依賴沖突。

Detector

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

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

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

    // visitMethodCall 回調(diào),當(dāng) lint 運(yùn)行到上面的方法調(diào)用時(shí),便會(huì)調(diào)用這個(gè)方法,我們?cè)谶@里處理檢測(cè)邏輯
    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        // 判斷方法是否是 android.util.Log 的方法
        if (context.evaluator.isMemberInClass(method, "android.util.Log")) {
            // 報(bào)告問(wèn)題,第一個(gè)參數(shù) ISSUE 是問(wèn)題的數(shù)據(jù),在后面定義,第二個(gè)參數(shù)是要報(bào)告的位置,可以理解為代碼的位置,后面是一個(gè) 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 后,我們要對(duì)規(guī)則進(jìn)行注冊(cè),這樣 IDE 或者 gradle lint,才能加載我們的 Detector。此處我們需要一個(gè) 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
    )
}

這個(gè)類非常簡(jiǎn)單,繼承了 IssueRegistry,并修改了 issues 字段,很容易理解這是在注冊(cè)這個(gè) ISSUE。

META-INF 配置

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

xyz.glorin.customlint.checker.CustomRegistry

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

Wrapper

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

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

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

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

App

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

implementation project(':wrapper')

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

./gradlew :app:lint

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

代碼

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

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

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

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