小酌雞湯
業(yè)精于勤而荒于嬉轴捎,行成于思而毀于隨憋他。
本文來源《Android 性能優(yōu)化 全家桶》
為什么要自定義 Lint 进陡?
?在某些特殊情況下释牺,系統(tǒng)定義的 Lint 并不能滿足我們的需求萝衩,這時需要我們自己定義規(guī)則,然后利用 Android 的 Lint 工具幫助我們自動發(fā)現(xiàn)某些問題没咙。
自定義 Lint 的原理
?主要使用LinkedIn發(fā)方案:將jar放到一個aar中猩谊。然后 Android 項目依賴這個 aar 完成自定義 lint 檢查(只對當(dāng)前項目有效)。
現(xiàn)在祭刚,就一起實操體驗 自定義 Lint ~
(1)lint實操環(huán)境(可選項牌捷,用自己的環(huán)境和代碼也一樣)
- SamplePop環(huán)境如下:
?Android Studio 4.0
?Gradle version 6.1.1
?Android API version 30
(2)創(chuàng)建Java工程,配置Gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.tools.lint:lint-api:26.0.0'
implementation 'com.android.tools.lint:lint-checks:26.0.0'
}
- lint-api: 官方給出的API(每個版本都會大變涡驮,對比了24暗甥,25,26捉捅,27這四個版本撤防,變化太大了)
- lint-checks:已有的檢查。
我在實操時遇到的問題:
- 我在配置Gradle文件時棒口,想著用最新的版本吧寄月,我就添加的是27的版本號。在編譯出的jar包放到
~/.android/lint/
后无牵,執(zhí)行l(wèi)int檢查剥懒,自己寫的Detector掃描器怎么都不生效,系統(tǒng)提示找不到對應(yīng)的SourceCodeScanner
類(這個要看你的Detector是實現(xiàn)的哪個lint-api)合敦。 - 我猜那可能是與系統(tǒng)自帶的掃描器版本不對應(yīng)啊,于是就查了下系統(tǒng)的自帶lint版本(系統(tǒng)自帶版本是26.0.0验游,我的gradle配置為27.0.0)充岛。修改gradle版本保檐,再次檢查,成功崔梗!
(3)創(chuàng)建Detector
?Detector負(fù)責(zé)掃描代碼夜只,發(fā)現(xiàn)問題并報告。
?下面掃描器的功能很簡單蒜魄,就是監(jiān)聽系統(tǒng)logandroid.util.Log的使用扔亥,lint檢查結(jié)果提示推薦使用 LogUtil 封裝類。
public class KJYRLogUtilDetector extends Detector implements Detector.UastScanner {
public static final Issue ISSUE = Issue.create(
"KJYRLogUtilCase",
"科技猿人-避免使用Log",
"使用LogUtil谈为,LogUtil對系統(tǒng)的Log類進行了邏輯封裝",
Category.SECURITY, 5, Severity.WARNING,
new Implementation(KJYRLogUtilDetector.class, Scope.JAVA_FILE_SCOPE));
public List<String> getApplicableMethodNames() {
return Arrays.asList("d", "e", "i", "v", "w");
}
@Override
public void visitMethod(JavaContext context, UCallExpression node, PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "android.util.Log")) {
String message = "kejiyuanren:請使用LogUtil封裝類";
context.report(ISSUE, node, context.getLocation(node), message);
}
}
}
?細(xì)則說明:
getApplicableMethodNames
:返回要監(jiān)聽的方法名稱集合context.report(ISSUE, node, context.getLocation(node), message)
:檢查結(jié)果上報(report會自動處理被suppress(suppressLint)/ignore(tools:ignore)的警告)Issue
:由靜態(tài)工廠方法創(chuàng)建旅挤,由Detector 發(fā)現(xiàn)并報告。
?Issue字段說明:
- id : 唯一值伞鲫,應(yīng)該能簡短描述當(dāng)前問題粘茄。利用Java注解或者XML屬性進行屏蔽時,使用的就是這個id秕脓。
- summary : 簡短的總結(jié)柒瓣,通常5-6個字符,描述問題而不是修復(fù)措施吠架。
- explanation : 完整的問題解釋和修復(fù)建議芙贫。
- category : 問題類別。詳見下文詳述部分傍药。
- priority : 優(yōu)先級磺平。1-10的數(shù)字,10為最重要/最嚴(yán)重怔檩。
- severity : 嚴(yán)重級別:Fatal, Error, Warning, Informational, Ignore褪秀。
- Implementation : 為Issue和Detector提供映射關(guān)系,Detector就是當(dāng)前Detector薛训。聲明掃描檢測的范圍Scope媒吗,Scope用來描述Detector需要分析時需要考慮的文件集,包括:Resource文件或目錄乙埃、Java文件闸英、Class文件。
(4)創(chuàng)建IssueRegistry
?提供需要被檢測的Issue列表
public class KJYRIssueRegistry extends IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(
KJYRLogUtilDetector.ISSUE,
KJYRSetTextDetector.SET_TEXT_I18N
// HandlerDetector.ISSUE
);
}
}
(5)jar包生成
?我們將寫好的掃描器打包成jar
jar {
manifest {
attributes("Lint-Registry": "com.kejiyuanren.lint_jar.KJYRIssueRegistry")
}
}
(6)jar包使用
?最終jar包是要提供給系統(tǒng)lint掃描的介袜,下面的兩個方案最明顯的區(qū)別就是影響范圍甫何。
(6.1)Google方案
//將jar拷貝到~/.android/lint中
$ mkdir ~/.android/lint/
$ cp lint_jar.jar ~/.android/lint/
?缺點:針對所有工程,會影響同一臺機器其他工程的Lint檢查遇伞。即便觸發(fā)工程時拷貝過去辙喂,執(zhí)行完刪除,但其他進程或線程使用./gradlew lint仍可能會受到影響。
(6.2)LinkedIn方案
?LinkedIn 提供了另一種思路 : 將jar放到一個aar中巍耗。這樣我們就可以針對工程進行自定義Lint秋麸,lint.jar只對當(dāng)前工程有效。
?lint_jar 工程:build.gradle
//注冊 KJYRIssueRegistry炬太,生成jar包
jar {
manifest {
attributes("Lint-Registry": "com.kejiyuanren.lint_jar.KJYRIssueRegistry")
}
}
//聲明依賴
configurations {
lintJarOutput
}
//依賴項實現(xiàn)
dependencies {
lintJarOutput files(jar)
}
?lint_aar 工程:build.gradle
//配置lint的jar包
configurations {
lintJarImport
}
//依賴項實現(xiàn)
dependencies {
lintJarImport project(path: ":lint_jar", configuration: "lintJarOutput")
}
//將jar復(fù)制到lint目錄下的lint.jar
task copyLintJar(type: Copy) {
from (configurations.lintJarImport) {
rename {
String fileName ->
'lint.jar'
}
}
into 'build/intermediates/lint/'
}
//將 copyLintJar 插入到 compileLint 之前執(zhí)行
project.afterEvaluate {
def compileLintTask = project.tasks.find { it.name == 'compileLint' }
compileLintTask.dependsOn(copyLintJar)
}
?lint 工程:build.gradle
dependencies {
//添加lint aar
implementation project(':lint_aar')
}
(7)lint檢查結(jié)果
(8)實踐總結(jié)
- lint的版本匹配問題灸蟆,如果遇到自定義lint不生效,請讓自己的lint版本與系統(tǒng)版本保持一致
- 多看lint-checks中的現(xiàn)有Detector亲族,從源碼中學(xué)到更多炒考,更系統(tǒng)
- 如果對自定義lint開發(fā)plugin感興趣,請參考文章結(jié)尾的: 美團 -> Android自定義Lint實踐
- 如果對debug lint代碼感興趣霎迫,請參考文章結(jié)尾的:優(yōu)秀文章
小編的擴展鏈接
參考鏈接
- 谷歌官網(wǎng) -> 這是應(yīng)該讀的第一篇文章
- 美團 -> Android自定義Lint實踐
- LinkedIn方案
- Android自定義lint規(guī)則
- 優(yōu)秀文章
- Android Lint工作原理剖析
新晴原野曠斋枢,極目無氛垢
?
舉手之勞,贊有余香女气!???比心??
?