自定義lint增量檢查

前言

??前一段時(shí)間將公司的代碼規(guī)范文檔翻譯為lint規(guī)則庫(kù)并在編譯時(shí)檢查。當(dāng)較小的項(xiàng)目上面運(yùn)行沒(méi)有感覺(jué)有什么問(wèn)題薄风,但是當(dāng)導(dǎo)入較大項(xiàng)目時(shí)抑月,就發(fā)現(xiàn)編譯速度明顯變慢,嚴(yán)重影響工作效率阿弃,因此就做了lint增量檢查诊霹。

??廢話少說(shuō),先上代碼IncrementLint渣淳,該項(xiàng)目的實(shí)現(xiàn)方案是參照Android Lint增量掃描實(shí)戰(zhàn)紀(jì)要脾还,該文檔上對(duì)實(shí)現(xiàn)原理描述得很詳細(xì),在此就不再重復(fù)入愧,在這里主要想與大家分享完成這個(gè)項(xiàng)目時(shí)遇到的一些問(wèn)題和解決方法并介紹一下grale的lintTask是如何接入到原本lint代碼的鄙漏。

問(wèn)題

  • 問(wèn)題一:找不到lint檢查的入口

??Android Lint增量掃描實(shí)戰(zhàn)紀(jì)要中介紹需要編寫(xiě)LintClinet類繼承LintGradleClient,但是LintGradleClient卻不知道在哪棺蛛。其實(shí)只要導(dǎo)入"com.android.tools.lint:lint-gradle:26.3.2"這個(gè)包怔蚌,就能找到LintGradleClient這個(gè)類。其實(shí)最開(kāi)始我也沒(méi)找到這個(gè)旁赊,后來(lái)看了sdk工具中l(wèi)int腳本桦踊,然后找到了{(lán)sdk}/tools/lib/lint-26.0.0-dev.jar,通過(guò)查看這個(gè)包里面的源碼终畅,看到了LintCliClient這個(gè)的使用方法和LintGradleClient很類似籍胯,最后通過(guò)其包名定位到最后的包。

??這一部分告訴大家一個(gè)小技巧(自學(xué)gradle离福,大神繞行)杖狼, 像com.android.*這些規(guī)范的包,其groupId與artifactId與它的包名有關(guān)聯(lián)(例如 LintGradleClient類的包名就是com.android.tools.lint.gradle妖爷,com.android.tools.lin是groupId蝶涩,lint-gradle是artifactId)。

  • 問(wèn)題二:實(shí)例化 LintGradleClient 參數(shù)較多不知如何獲取

??LintGradleClient繼承實(shí)現(xiàn)了LintCliClient赠涮,在lint-26.0.0-dev.jar中有LintCliClient的初始化和使用子寓,如果定制非gradle版本的lint工具,可以參考這個(gè)(gradle版本lint工具忽略)笋除。

??eagleeye-gradle-codescanning中有實(shí)例化LintGradleClient所需各個(gè)參數(shù)的獲取方法斜友,但是很多都是使用反射來(lái)實(shí)現(xiàn)。既然我們要在gradle中執(zhí)行l(wèi)int垃它,處于gradle環(huán)境中也就沒(méi)必要需要反射調(diào)用了鲜屏。在 gradle中的lint task執(zhí)行是由LintBaseTask這里開(kāi)始執(zhí)行的,因此查看這部分流程(后面會(huì)分析)就能幫助我們完成各個(gè)參數(shù)国拇,具體請(qǐng)參照IncrementLint

  • 問(wèn)題三:如何做增量

??原則上增量是需要在全量的基礎(chǔ)上的洛史,但是基本上我們的代碼都是有g(shù)it或svn工具管理的,因此可以直接使用使用該工具做差量酱吝。另外也殖,由于git或svn都只是管理java代碼或資源文件,導(dǎo)致自定義的lint無(wú)法檢查class文件。如果自定義的規(guī)則里面有關(guān)于class文件檢查的部分忆嗜,這種增量方式是無(wú)法使用的己儒。

LintTask流程

??AS中有很多的lint任務(wù)包括lintDebug、lintRelease等捆毫,每在productFlavors添加一個(gè)Flavor就會(huì)多出對(duì)應(yīng)的lint{FlavorName}Debug和lint{FlavorName}Release闪湾,這些都是由LintBaseTask衍生出的。我們先看LintBaseTask源碼:

public abstract class LintBaseTask extends DefaultTask {
 ......
 //task執(zhí)行l(wèi)int入口
 protected void runLint(LintBaseTaskDescriptor descriptor) {
   FileCollection lintClassPath = getLintClassPath();
   if (lintClassPath != null) {
     new ReflectiveLintRunner().runLint(getProject().getGradle(),descriptor,lintClassPath.getFiles());
   }
 }

 //獲取BuildTools LintGradleClient所需參數(shù)之一
 private BuildToolInfo getBuildTools() {
   TargetInfo targetInfo = androidBuilder.getTargetInfo();
   Preconditions.checkState(
   targetInfo != null, "androidBuilder.targetInfo required for task '%s'.", getName());
   return targetInfo.getBuildTools();
 }

 //LintBaseTask描述者绩卤,在runLint中使用途样,后續(xù)實(shí)例化LintGradleClient的各個(gè)參數(shù)都是由這里獲取
 protected abstract class LintBaseTaskDescriptor extends com.android.tools.lint.gradle.api.LintExecutionRequest {
 ......
 }

 //獲取VariantInputs LintGradleClient所需參數(shù)之一,直接new VariantInputs()就行了
 public static class VariantInputs implements com.android.tools.lint.gradle.api.VariantInputs {
 ......
 }

 //該類還不知道干嘛用的濒憋,之前提到的lint{FlavorName}Debug任務(wù)應(yīng)該就是和這個(gè)有關(guān)何暇,不知哪位大神知道,麻煩告知一下
 public abstract static class BaseCreationAction<T extends LintBaseTask> extends TaskCreationAction<T> {

   @Override
   public void configure(@NonNull T lintTask) {
     lintTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
     lintTask.lintOptions = globalScope.getExtension().getLintOptions();
     File sdkFolder = globalScope.getSdkHandler().getSdkFolder();
     if (sdkFolder != null) {
       lintTask.sdkHome = sdkFolder;
     }
     lintTask.toolingRegistry = globalScope.getToolingRegistry();
     lintTask.reportsDir = globalScope.getReportsDir();
     lintTask.androidBuilder = globalScope.getAndroidBuilder();
     lintTask.lintClassPath = globalScope.getProject().getConfigurations().getByName(LINT_CLASS_PATH);
   }
 }
}

??runLint就是task執(zhí)行的操作跋炕,它其實(shí)就初始化了ReflectiveLintRunner赖晶,然后把LintBaseTaskDescriptor傳入律适。之后初始化LintGradleClient需要的參數(shù)大部分都是由LintBaseTaskDescriptor這個(gè)對(duì)象獲取的辐烂。

??ReflectiveLintRunner這個(gè)類在lint-gradle-api包中,這個(gè)類的runLint方法就利用反射實(shí)例化LintGradleExecution對(duì)象捂贿,并調(diào)用analyze方法纠修。

fun runLint(gradle: Gradle, request: LintExecutionRequest, lintClassPath: Set<File>) {
 .....
 val loader = getLintClassLoader(gradle, lintClassPath)
 val cls = loader.loadClass("com.android.tools.lint.gradle.LintGradleExecution")
 val constructor = cls.getConstructor(LintExecutionRequest::class.java)
 val driver = constructor.newInstance(request)
 val analyzeMethod = driver.javaClass.getDeclaredMethod("analyze")
 analyzeMethod.invoke(driver)
 ......
}

??LintGradleExecution在lint-gradle包中,LintGradleClient也在這里厂僧。analyze方法獲取了ToolingRegistry和AndroidProject扣草,并根據(jù)變種(variant)走不同的LintGradleClient初始化參數(shù)獲取流程。

public class LintGradleExecution {
 .......
 public void analyze() throws IOException {
 //最后獲取的是LintBaseTask的toolingRegistry颜屠,該值在BaseCreationAction中configure中設(shè)置到LintBaseTask中
 ToolingModelBuilderRegistry toolingRegistry = this.descriptor.getToolingRegistry();  
 if (toolingRegistry != null) {
   AndroidProject modelProject = createAndroidProject(this.descriptor.getProject(), toolingRegistry);
   String variantName = this.descriptor.getVariantName();
   if (variantName != null) {
     Iterator var4 = modelProject.getVariants().iterator();
     while(var4.hasNext()) {
       Variant variant = (Variant)var4.next();
       if (variant.getName().equals(variantName)) {
         this.lintSingleVariant(variant);
         return;
       }
     }
   } else {
     this.lintAllVariants(modelProject);
  }
 } else {
   this.lintNonAndroid();
 }
}

 //創(chuàng)建AndroidProject LintGradleClient所需參數(shù)之一
protected static AndroidProject createAndroidProject(Project gradleProject,ToolingModelBuilderRegistry toolingRegistry) {
 String modelName = AndroidProject.class.getName();
 ToolingModelBuilder modelBuilder = toolingRegistry.getBuilder(modelName);
 assert modelBuilder.canBuild(modelName) : modelName;
 ExtraPropertiesExtension ext = gradleProject.getExtensions().getExtraProperties();
 synchronized(ext) {
 ext.set("android.injected.build.model.only.versioned", Integer.toString(3));
 ext.set("android.injected.build.model.disable.src.download", true);
 AndroidProject var6;
 try {
   var6 = (AndroidProject)modelBuilder.buildAll(modelName, gradleProject);
 } finally {
   ext.set("android.injected.build.model.only.versioned", (Object)null);
   ext.set("android.injected.build.model.disable.src.download", (Object)null);
 }
 return var6;
 }
}
.....
private Pair<List<Warning>, LintBaseline> runLint(Variant variant, VariantInputs variantInputs, boolean report, boolean isAndroid, boolean allowFix) {
 IssueRegistry registry = createIssueRegistry(isAndroid);
 LintCliFlags flags = new LintCliFlags();
 LintGradleClient client = new LintGradleClient(this.descriptor.getGradlePluginVersion(), registry, flags, this.descriptor.getProject(), this.descriptor.getSdkHome(), variant, variantInputs, this.descriptor.getBuildTools(), isAndroid, variant != null ? variant.getName() : null);
 ......
 LintOptions lintOptions = this.descriptor.getLintOptions();
 boolean fix = false;
 if (lintOptions != null) {
 syncOptions(lintOptions, client, flags, variant, this.descriptor.getProject(), this.descriptor.getReportsDir(), report, fatalOnly, allowFix);
 } else {
 .....
 }
 ......
 warnings = client.run(registry);
 .....
 }
}

??lintSingleVariant辰妙、lintAllVariants、lintNonAndroid最后都是進(jìn)入到runLint甫窟,在這個(gè)函數(shù)中進(jìn)行LintGradleClient對(duì)象初始化并獲取在build.gradle中配置lintOptions密浑,然后執(zhí)行LintGradleClient對(duì)象的run方法,到這就進(jìn)入到了lint檢查流程粗井,后續(xù)gradle沒(méi)有關(guān)系了尔破,具體lint檢查流程參考Android Lint工作原理剖析.

修復(fù)問(wèn)題

1、插件在kotlin和java混合項(xiàng)目上報(bào)錯(cuò)

??由于插件中的kotlin版本和項(xiàng)目的相互沖突浇衬,導(dǎo)致插件會(huì)在執(zhí)行l(wèi)int檢查的時(shí)候報(bào)錯(cuò)懒构。參考gradle源碼中寫(xiě)法,使用ClassLoader將lint的執(zhí)行環(huán)境隔離開(kāi)耘擂,這樣就能解決kotlin版本沖突胆剧。具體修改參照LintRunner.java

2、自定義的lint規(guī)則無(wú)法使用

??http://www.reibang.com/p/01f813c09589

參考

1醉冤、Android Lint增量掃描實(shí)戰(zhàn)紀(jì)要
2秩霍、Android Lint工作原理剖析
3滚朵、eagleeye-gradle-codescanning

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市前域,隨后出現(xiàn)的幾起案子辕近,更是在濱河造成了極大的恐慌,老刑警劉巖匿垄,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件移宅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡椿疗,警方通過(guò)查閱死者的電腦和手機(jī)漏峰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)届榄,“玉大人浅乔,你說(shuō)我怎么就攤上這事÷撂酰” “怎么了靖苇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)班缰。 經(jīng)常有香客問(wèn)我贤壁,道長(zhǎng),這世上最難降的妖魔是什么埠忘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任脾拆,我火速辦了婚禮,結(jié)果婚禮上莹妒,老公的妹妹穿的比我還像新娘名船。我一直安慰自己,他們只是感情好旨怠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布渠驼。 她就那樣靜靜地躺著,像睡著了一般运吓。 火紅的嫁衣襯著肌膚如雪渴邦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天拘哨,我揣著相機(jī)與錄音谋梭,去河邊找鬼。 笑死倦青,一個(gè)胖子當(dāng)著我的面吹牛瓮床,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼隘庄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踢步!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起丑掺,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤获印,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后街州,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兼丰,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年唆缴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳍征。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡面徽,死狀恐怖艳丛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趟紊,我是刑警寧澤氮双,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站织阳,受9級(jí)特大地震影響眶蕉,放射性物質(zhì)發(fā)生泄漏砰粹。R本人自食惡果不足惜唧躲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碱璃。 院中可真熱鬧弄痹,春花似錦、人聲如沸嵌器。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爽航。三九已至蚓让,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讥珍,已是汗流浹背历极。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衷佃,地道東北人趟卸。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親锄列。 傳聞我的和親對(duì)象是個(gè)殘疾皇子图云,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

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