前言
??前一段時(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