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 建立線程,提示應該使用統一線程池。
先來看一個簡單的自定義 Lint 是怎麼樣一步一步寫出來的,該例子實際上來自 github.com/googlesampl… ,該 Rep 就一個 SampleCodeDetector,也就是上文中所說的。
自定義 Lint 一共能夠分爲四步:
在 build.gradle 文件裏添加依賴:
compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"複製代碼
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 錯誤。
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_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 就寫完了,可是有點意猶未盡的感受。這時候就須要你發揮想象力,想一想本身須要什麼。你能夠參考我給的源碼,或者參考 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)
}複製代碼
(逃~