自定義lint規(guī)則不生效

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-gradlelint-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ì)修改暮蹂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寞缝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仰泻,更是在濱河造成了極大的恐慌荆陆,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件集侯,死亡現(xiàn)場(chǎng)離奇詭異被啼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)棠枉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門浓体,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人辈讶,你說(shuō)我怎么就攤上這事命浴。” “怎么了贱除?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵生闲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我月幌,道長(zhǎng)跪腹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任飞醉,我火速辦了婚禮冲茸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缅帘。我一直安慰自己轴术,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布钦无。 她就那樣靜靜地躺著逗栽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪失暂。 梳的紋絲不亂的頭發(fā)上彼宠,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音弟塞,去河邊找鬼凭峡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛决记,可吹牛的內(nèi)容都是我干的摧冀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼索昂!你這毒婦竟也來(lái)了建车?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椒惨,失蹤者是張志新(化名)和其女友劉穎缤至,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體康谆,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡领斥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秉宿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屯碴,死狀恐怖描睦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情导而,我是刑警寧澤忱叭,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站今艺,受9級(jí)特大地震影響韵丑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虚缎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一撵彻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧实牡,春花似錦陌僵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至题涨,卻和暖如春偎谁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纲堵。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工巡雨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人席函。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓鸯隅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝌以,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容