1默蚌、說(shuō)明
??最近一段時(shí)間冻晤,項(xiàng)目遷移到AndroidX后,突然發(fā)現(xiàn)之前編寫的lint規(guī)則沒(méi)有生效绸吸。查看lint報(bào)告提示lintError鼻弧,后面說(shuō)明就反饋找不到自定義規(guī)則對(duì)應(yīng)的ID。
2锦茁、問(wèn)題
??經(jīng)過(guò)gradle調(diào)試(gradle調(diào)試方法自行百度)發(fā)現(xiàn)目前使用的lint版本(26.2.1)不沒(méi)有適應(yīng)新的Lint規(guī)則導(dǎo)入(META-INF/services/com.android.tools.lint.client.api.IssueRegistry)攘轩。但是AndroidX中很多包都使用這種方式添加lint規(guī)則以保證該包被正確使用。
JarFileIssueRegistry代碼如下:
private fun findRegistries(
client: LintClient,
jarFiles: Collection<File>
): Map<String, File> {
val registryClassToJarFile = HashMap<String, File>()
for (jarFile in jarFiles) { //1码俩、遍歷lint規(guī)則的jar包
JarFile(jarFile).use { file ->
val manifest = file.manifest
val attrs = manifest.mainAttributes
var attribute: Any? = attrs[Attributes.Name(MF_LINT_REGISTRY)]
var isLegacy = false
if (attribute == null) {
attribute = attrs[Attributes.Name(MF_LINT_REGISTRY_OLD)]
if (attribute != null) {
isLegacy = true
}
}
//2度帮、AndroidX包中帶的規(guī)則包并不包含上面兩種屬性,因此執(zhí)行else代碼
if (attribute is String) {
val className = attribute
if (!isLegacy || registryClassToJarFile[className] == null) {
registryClassToJarFile[className] = jarFile
}
} else {
val services = file.getJarEntry(SERVICE_KEY)
if (services != null) {
...
//3稿存、發(fā)現(xiàn)META-INF/services/com.android.tools.lint.client.api.IssueRegistry定義的規(guī)則之后立即返回
return registryClassToJarFile
}
client.log(
Severity.ERROR, null,
"Custom lint rule jar %1\$s does not contain a valid " +
"registry manifest key (%2\$s).\n" +
"Either the custom jar is invalid, or it uses an outdated " +
"API not supported this lint client",
jarFile.path, MF_LINT_REGISTRY
)
}
}
}
return registryClassToJarFile
}
??由上述代碼中的三處注釋可以看出笨篷,lint規(guī)則jar包并沒(méi)有全部遍歷完成就直接退出了甫菠,我們添加的自定義規(guī)則并沒(méi)獲取到。
3冕屯、解決
??講到這里可能就有人覺(jué)得這個(gè)問(wèn)題更新一下版本不就能很好解決了。Lint-27.1.0版本確實(shí)是沒(méi)有這樣的問(wèn)題的拂苹,因?yàn)樗サ袅?strong>return registryClassToJarFile這段代碼安聘,遍歷了所有的jar包。但是我們項(xiàng)目中對(duì)lint流程做了定制瓢棒,讓它能夠?qū)崿F(xiàn)增量檢查(如需實(shí)現(xiàn)Lint增量檢查請(qǐng)看自定義lint增量檢查)浴韭。更新版本需要重新適配,還需要更新gradle插件版本脯宿,導(dǎo)致別的插件都需要進(jìn)行相應(yīng)修改念颈。這樣的改動(dòng)實(shí)在太大,風(fēng)險(xiǎn)很高连霉,讓人無(wú)法接受榴芳。那么我們這么解決這個(gè)問(wèn)題呢?
??最開始解決這個(gè)問(wèn)題的思路是將自定義的規(guī)則放在其他規(guī)則的前面跺撼,讓它的規(guī)則提前添加registryClassToJarFile中窟感,但是發(fā)現(xiàn)jarFiles是一個(gè)Set集合,沒(méi)有固定點(diǎn)的順序歉井。而且這樣的方案還有一個(gè)弊端柿祈,那就是部分AndroidX包的規(guī)則并不會(huì)執(zhí)行,也就是即使我們使用AndroidX有錯(cuò)誤哩至,Lint也檢查不出來(lái)問(wèn)題躏嚎。
??前面方案的失敗讓我打算將整個(gè)lint包(包括lint-gradle,lint-gradle-api)都直接到導(dǎo)入到項(xiàng)目中菩貌,然后直接修改代碼來(lái)解決問(wèn)題卢佣。但是突然想到之前解決kotlin項(xiàng)目編譯報(bào)錯(cuò)的時(shí)候,利用ClassLoader將Lint執(zhí)行的流程置于獨(dú)立的環(huán)境(具體參考LintRunner.java)箭阶。使用這個(gè)ClassLoader可以將JarFileIssueRegistry這個(gè)類替換成我們自己的類JarFileIssueRegistry.kt(刪除了return registryClassToJarFile代碼)珠漂。具體如何實(shí)現(xiàn)呢?那就是初始化ClassLoader時(shí)將我們的jar包放在最前面尾膊,ClassLoader加載JarFileIssueRegistry這個(gè)類的時(shí)候優(yōu)先加載我們編寫的類媳危。
private static ClassLoader getLintClassLoader(Gradle gradle, Set<File> lintClassPath, String customJarName) {
DelegatingClassLoader l = loader;
if (l == null) {
List<URL> urls = computeUrlsFallback(lintClassPath, customJarName);
l = new DelegatingClassLoader(urls.toArray(new URL[0]));
loader = l;
}
......
}
private static List<URL> computeUrlsFallback(Set<File> lintClassPath, String customJarName) {
List<URL> urls = new ArrayList<>();
Iterator<File> iterator = lintClassPath.iterator();
//優(yōu)先加載包含修改的JarFileIssueRegistry.kt的jar包
while (iterator.hasNext()) {
File file = iterator.next();
if (file.getName().startsWith(customJarName)) {
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
}
iterator.remove();
}
}
lintClassPath.forEach(file -> {
String name = file.getName();
if (name.startsWith("uast-") ||
name.startsWith("intellij-core-") ||
name.startsWith("kotlin-compiler-") ||
name.startsWith("asm-") ||
name.startsWith("kxml2-") ||
name.startsWith("trove4j-") ||
name.startsWith("groovy-all-") |
// All the lint jars, except lint-gradle-api jar (self)
name.startsWith("lint-") &&
// Do *not* load this class in a new class loader; we need to
// share the same class as the one already loaded by the Gradle plugin
!name.startsWith("lint-gradle-api-")
)
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
});
return urls;
}
??至此自定義規(guī)則不生效的問(wèn)題就解決了,這樣既能確認(rèn)自定義規(guī)則正常執(zhí)行冈敛,也能保證AndroidX中的規(guī)則包能夠正常添加待笑。同時(shí)如果后續(xù)需要升級(jí)版本,只需要進(jìn)行少量文件移植抓谴,無(wú)需進(jìn)行大量比對(duì)修改暮蹂。