自定義 Lint,基於 AS 3.x API

前言

Lint 是 Android Studio 內置的一個靜態代碼掃描工具,它能夠檢查 Android 項目源文件是否包含潛在錯誤,以及在正確性、安全性、性能、易用性、便利性和國際化方面是否須要優化改進。AS 已經內置了大量的 Lint 檢查規則,可是當咱們須要定製化規則時,就須要考慮自定義 Lint 了。html

首先,先要仔細想一想,何時須要用到自定義 Lint 呢?java

這就要發揮你的想象力了,我最初是想推 webp,用以替換掉 png,這樣就須要開發者每次自覺的將 png 轉化爲 webp,可是又不可能結對編程,監督他進行轉化,因此自定義 Lint 就派上用場了。android

可是,目前網上的絕大多數博客都還停留在 AS 2.x 的 Lint API 上,關於 AS 3.x Lint API 的幾乎是空白。從 AS 2.x 到 AS 3.x,Lint API 變化還挺大的,特別是對 Java 源文件的檢測,從 JavaScanner 過渡到 JavaPsiScanner 再到 UastScanner,自定義 Lint 的註冊以及引用方式也都發生了變化。本文也是從 Google 的一個 Sample 再參考系統內置的 Lint 實現,一點點摸索出來的。git

如下是項目中的自定義 Lint 實現效果:github

以上 Detector 都有什麼用呢?web

ToastDetector:Android 自帶的 Lint 規則,用於提示 Toast 忘記調用 show 方法,在 BuiltinIssueRegistry 類能夠查看內置的全部的 Detector,這也是我實現自定義 Lint 的主要參考依據。express

SampleCodeDetector:Google Sample 中的 Demo,用於檢測文本表達式中是否包含特定的字符串。編程

PngDetector:用於檢測全部 layout 或 java 文件中引用的 png 資源,提示使用 webp。api

LogDetector:用於檢測使用 Log 類的 i、d、e 等日誌輸出的方法,提示使用統一的日誌工具類。嚴重程度爲 Error,因此默認狀況下,會中斷編譯流程。若是嚴重程度爲 Warning,僅僅是報黃警告。安全

ThreadDetector:用於檢測直接經過 new Thread 建立線程,提示應該使用統一線程池。

源碼地址:github.com/Omooo/Custo…

正文

先來看一個簡單的自定義 Lint 是怎麼樣一步一步寫出來的,該例子實際上來自 github.com/googlesampl… ,該 Rep 就一個 SampleCodeDetector,也就是上文中所說的。

自定義 Lint 一共能夠分爲四步:

第一步:建立 java library 工程

在 build.gradle 文件裏添加依賴:

compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"複製代碼

第二步:建立 Detector

public class SampleDetector extends Detector implements Detector.UastScanner {
​
    //第一步:定義 ISSUE
    public static final Issue ISSUE = Issue.create(
            "ShortUniqueId",    //惟一 ID
            "Lint Mentions",    //簡單描述
            "Blah blah blah.",  //詳細描述
            Category.CORRECTNESS,   //問題種類(正確性、安全性等)
            6,  //權重
            Severity.WARNING,   //問題嚴重程度(忽略、警告、錯誤)
            new Implementation(     //實現,包括處理實例和做用域
                    SampleCodeDetector.class,
                    Scope.JAVA_FILE_SCOPE));
​
    //第二步:定義檢測類型以及處理邏輯
    //檢測類型包括文本表達式、調用相關表達式等
    @Override
    public List<Class<? extends UElement>> getApplicableUastTypes() {
        return Collections.singletonList(ULiteralExpression.class);
    }
​
    @Override
    public UElementHandler createUastHandler(@NotNull JavaContext context) {
        return new UElementHandler() {
            @Override
            public void visitLiteralExpression(@NotNull ULiteralExpression expression) {
                String string = UastLiteralUtils.getValueIfStringLiteral(expression);
                if (string == null) {
                    return;
                }
                if (string.contains("Omooo") && string.matches(".*\\bOmooo\\b.*")) {
                    //第三步:符合條件,上報 ISSUE
                    context.report(ISSUE, expression, context.getLocation(expression),
                            "This code mentions `Omooo`");
                }
            }
        };
    }
}複製代碼

Lint API 中內置了不少 Scanner:

Scanner 類型 Desc
UastScanner 掃描 Java、Kotlin 源文件
XmlScanner 掃描 XML 文件
ResourceFolderScanner 掃描資源文件夾
ClassScanner 掃描 Class 文件
BinaryResourceScanner 掃描二進制資源文件

更多請參考:static.javadoc.io/com.android…

注意:

這裏須要注意的一點是,若是對應的 ISSUE 嚴重程度爲錯誤(Severity.ERROR),那麼在默認狀況下,會中斷編譯流程,固然,你也能夠配置 LintOptions 來抑制 Lint 錯誤。

第三步:註冊 Detector

public class CustomIssueRegistry extends IssueRegistry {
​
    @NotNull
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(
                SampleCodeDetector.ISSUE);
    }
​
    @Override
    public int getApi() {
        return ApiKt.CURRENT_API;
    }
}複製代碼

這裏能夠註冊多個 Detector,目前最新版本的 Lint 內置了 360 種 Detector,都在 BuiltinIssueRegistry 類中,能夠做爲咱們編寫自定義 Lint 的最佳參考案例。

第四步:引入自定義 Lint

首先須要在 lint_library 中的 build.gradle 文件中添加,完整代碼爲:

apply plugin: 'java-library'
​
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
​
    compileOnly "com.android.tools.lint:lint-api:26.4.1"
    compileOnly "com.android.tools.lint:lint-checks:26.4.1"
}
​
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
​
jar {
    manifest {
        attributes("Lint-Registry-v2": "top.omooo.lint_library.CustomIssueRegistry")
    }
}複製代碼

而後就能夠在 app module 中的 build.gradle 中引入並使用了:

dependencies {
    //...
    lintChecks project(":lint_library")
}複製代碼

Lint 進階

以上,一個簡單的自定義 Lint 就寫完了,可是有點意猶未盡的感受。這時候就須要你發揮想象力,想一想本身須要什麼。你能夠參考我給的源碼,或者參考 Android 內置的 Lint 源碼,看看它們能作什麼。

這一小節很重要,可是我並不會給你講如何去實現某某功能,本身看源碼學習,由於真的不難哇。

最後

若是你很懶,很煩每次都敲一遍 ./gradlew lint 去查看 Lint 輸出,那麼能夠把執行 Lint 任務掛載在每次安裝 Debug 包以前,即:

/**
 * 在執行 assembleDebug Task 以前掛載 lintDebug
 */
project.afterEvaluate {
    def assembleDebugTask = project.tasks.find { it.name == 'assembleDebug' }
    def lintTask = project.tasks.find { it.name == 'lintDebug' }
    assembleDebugTask.dependsOn(lintTask)
}複製代碼

(逃~

相關文章
相關標籤/搜索