本文已同步發(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)建兩個模塊:
- checker 模塊,是一個 Java Library
- 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)的提示了。