前言
前一段时间将公司的代码规范文档翻译为lint规则库并在编译时检查。当较小的项目上面运行没有感觉有什么问题,但是当导入较大项目时,就发现编译速度明显变慢,严重影响工作效率,因此就做了lint增量检查。
废话少说,先上代码IncrementLint,该项目的实现方案是参照Android Lint增量扫描实战纪要,该文档上对实现原理描述得很详细,在此就不再重复,在这里主要想与大家分享完成这个项目时遇到的一些问题和解决方法并介绍一下grale的lintTask是如何接入到原本lint代码的。
问题
- 问题一:找不到lint检查的入口
Android Lint增量扫描实战纪要中介绍需要编写LintClinet类继承LintGradleClient,但是LintGradleClient却不知道在哪。其实只要导入"com.android.tools.lint:lint-gradle:26.3.2"这个包,就能找到LintGradleClient这个类。其实最开始我也没找到这个,后来看了sdk工具中lint脚本,然后找到了{sdk}/tools/lib/lint-26.0.0-dev.jar,通过查看这个包里面的源码,看到了LintCliClient这个的使用方法和LintGradleClient很类似,最后通过其包名定位到最后的包。
这一部分告诉大家一个小技巧(自学gradle,大神绕行), 像com.android.*这些规范的包,其groupId与artifactId与它的包名有关联(例如 LintGradleClient类的包名就是com.android.tools.lint.gradle,com.android.tools.lin是groupId,lint-gradle是artifactId)。
- 问题二:实例化 LintGradleClient 参数较多不知如何获取
LintGradleClient继承实现了LintCliClient,在lint-26.0.0-dev.jar中有LintCliClient的初始化和使用,如果定制非gradle版本的lint工具,可以参考这个(gradle版本lint工具忽略)。
eagleeye-gradle-codescanning中有实例化LintGradleClient所需各个参数的获取方法,但是很多都是使用反射来实现。既然我们要在gradle中执行lint,处于gradle环境中也就没必要需要反射调用了。在 gradle中的lint task执行是由LintBaseTask这里开始执行的,因此查看这部分流程(后面会分析)就能帮助我们完成各个参数,具体请参照IncrementLint
- 问题三:如何做增量
原则上增量是需要在全量的基础上的,但是基本上我们的代码都是有git或svn工具管理的,因此可以直接使用使用该工具做差量。另外,由于git或svn都只是管理java代码或资源文件,导致自定义的lint无法检查class文件。如果自定义的规则里面有关于class文件检查的部分,这种增量方式是无法使用的。
LintTask流程
AS中有很多的lint任务包括lintDebug、lintRelease等,每在productFlavors添加一个Flavor就会多出对应的lint{FlavorName}Debug和lint{FlavorName}Release,这些都是由LintBaseTask衍生出的。我们先看LintBaseTask源码:
public abstract class LintBaseTask extends DefaultTask {
......
//task执行lint入口
protected void runLint(LintBaseTaskDescriptor descriptor) {
FileCollection lintClassPath = getLintClassPath();
if (lintClassPath != null) {
new ReflectiveLintRunner().runLint(getProject().getGradle(),descriptor,lintClassPath.getFiles());
}
}
//获取BuildTools LintGradleClient所需参数之一
private BuildToolInfo getBuildTools() {
TargetInfo targetInfo = androidBuilder.getTargetInfo();
Preconditions.checkState(
targetInfo != null, "androidBuilder.targetInfo required for task '%s'.", getName());
return targetInfo.getBuildTools();
}
//LintBaseTask描述者,在runLint中使用,后续实例化LintGradleClient的各个参数都是由这里获取
protected abstract class LintBaseTaskDescriptor extends com.android.tools.lint.gradle.api.LintExecutionRequest {
......
}
//获取VariantInputs LintGradleClient所需参数之一,直接new VariantInputs()就行了
public static class VariantInputs implements com.android.tools.lint.gradle.api.VariantInputs {
......
}
//该类还不知道干嘛用的,之前提到的lint{FlavorName}Debug任务应该就是和这个有关,不知哪位大神知道,麻烦告知一下
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执行的操作,它其实就初始化了ReflectiveLintRunner,然后把LintBaseTaskDescriptor传入。之后初始化LintGradleClient需要的参数大部分都是由LintBaseTaskDescriptor这个对象获取的。
ReflectiveLintRunner这个类在lint-gradle-api包中,这个类的runLint方法就利用反射实例化LintGradleExecution对象,并调用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,并根据变种(variant)走不同的LintGradleClient初始化参数获取流程。
public class LintGradleExecution {
.......
public void analyze() throws IOException {
//最后获取的是LintBaseTask的toolingRegistry,该值在BaseCreationAction中configure中设置到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();
}
}
//创建AndroidProject LintGradleClient所需参数之一
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最后都是进入到runLint,在这个函数中进行LintGradleClient对象初始化并获取在build.gradle中配置lintOptions,然后执行LintGradleClient对象的run方法,到这就进入到了lint检查流程,后续gradle没有关系了,具体lint检查流程参考Android Lint工作原理剖析.
修复问题
1、插件在kotlin和java混合项目上报错
由于插件中的kotlin版本和项目的相互冲突,导致插件会在执行lint检查的时候报错。参考gradle源码中写法,使用ClassLoader将lint的执行环境隔离开,这样就能解决kotlin版本冲突。具体修改参照LintRunner.java
2、自定义的lint规则无法使用
https://www.jianshu.com/p/01f813c09589
参考
1、Android Lint增量扫描实战纪要
2、Android Lint工作原理剖析
3、eagleeye-gradle-codescanning