你必須要了解的 AndroidStudio 3.x Lint API

你必須要了解的 AndroidStudio 3.x Lint API

前言

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

首先,先要仔細想想,什麼時候需要用到自定義 Lint 呢?

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

但是,目前網上的絕大多數博客都還停留在 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 實現,一點點摸索出來的。

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

你必須要了解的 AndroidStudio 3.x Lint API

以上 Detector 都有什麼用呢?

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

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

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

LogDetector:用於檢測使用 Log 類的 i、d、e 等日誌輸出的方法,提示使用統一的日誌工具類。嚴重程度為 Error,所以默認情況下,會中斷編譯流程。如果嚴重程度為 Warning,僅僅是報黃警告。

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

源碼地址:https://github.com/Omooo/CustomLint

正文

先來看一個簡單的自定義 Lint 是怎麼樣一步一步寫出來的,該例子實際上來自 https://github.com/googlesamples/android-custom-lint-rules ,該 Rep 就一個 SampleCodeDetector,也就是上文中所說的。

自定義 Lint 一共可以分為四步:

第一步:創建 java library 工程

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

<code>compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"/<code>

第二步:創建 Detector

<code>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>> 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`");
                }
            }
        };
    }
}/<class>/<code>

Lint API 中內置了很多 Scanner:

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

更多請參考:https://static.javadoc.io/com.android.tools.lint/lint-api/25.3.0/com/android/tools/lint/detector/api/package-summary.html

注意:

這裡需要注意的一點是,如果對應的 ISSUE 嚴重程度為錯誤(Severity.ERROR),那麼在默認情況下,會中斷編譯流程,當然,你也可以配置 LintOptions 來抑制 Lint 錯誤。

第三步:註冊 Detector

<code>public class CustomIssueRegistry extends IssueRegistry {

    @NotNull
    @Override
    public List<issue> getIssues() {

        return Arrays.asList(
                SampleCodeDetector.ISSUE);
    }

    @Override
    public int getApi() {
        return ApiKt.CURRENT_API;
    }
}/<issue>/<code>

這裡可以註冊多個 Detector,目前最新版本的 Lint 內置了 360 種 Detector,都在 BuiltinIssueRegistry 類中,可以作為我們編寫自定義 Lint 的最佳參考案例。

第四步:引入自定義 Lint

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

<code>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")
    }
}/<code>

然後就可以在 app module 中的 build.gradle 中引入並使用了:

<code>dependencies {
    lintChecks project(":lint_library")
}/<code>

Lint 進階

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

這一小節很重要,但是我並不會給你講如何去實現某某功能,自己看源碼學習,因為真的不難哇。

最後

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

<code>/**
 * 在執行 assembleDebug Task 之前掛載 lintDebug
 */
project.afterEvaluate {
    def assembleDebugTask = project.tasks.find { it.name == 'assembleDebug' }
    def lintTask = project.tasks.find { it.name == 'lintDebug' }
    assembleDebugTask.dependsOn(lintTask)
}/<code>

文章不易,如果大家喜歡這篇文章,或者對你有幫助希望大家多多,點贊,轉發,關注 哦。文章會持續更新的。絕對乾貨!!!



分享到:


相關文章: