gradle编译时注解增量教程
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gradle编译时注解增量教程相关的知识,希望对你有一定的参考价值。
前言
读者需要对apt有一定了解否则可能看不懂,这里提供两篇博主的文章:
注解处理器常用类说明
Java Pluginable Annotation processing
增量注解的定义和作用:
从Gradle 4.7
开始,gradle
提供了增量apt
,可以使上层开发者更快的编译.
笔者使用kapt
为例,我们使用apt生成的来会放入/app/build/generated/source/kapt
文件夹下:
博主写了一个apt,回使每个被@MyAnnotation
注释的类生成一个对应的新java文件.
@MyAnnotation()
public class MyFour {
int i1 =2;
}
@MyAnnotation()
public class MyTwo {
int i1 =2;
}
@MyAnnotation()
public class MyThree {
int i1 =2;
}
/app/build/generated/source/kapt
文件的生成文件:
当我们取消MyFour
的注解如下所示,再次编译
//@MyAnnotation()
public class MyFour {
int i1 =2;
}
@MyAnnotation()
public class MyTwo {
int i1 =2;
}
@MyAnnotation()
public class MyThree {
int i1 =2;
}
你会发现/app/build/generated/source/kapt
整个文件夹都会被删除,
在大型工程时这是一种不可接受的现象,每次改动相关代码会带来大量的编译时间消耗.这对这个问题我们希望之前的文件夹内容仅改动变化,而非重建删除.
这对上诉问题Gradle
提供了相关解决方案,只需要注解开发者提供一些基础信息给Gradle
即可.
Gradle 增量注解教程
Gradle
增量注解处理器分为两种isolating
,aggregating
.你可以在resources/META-INF/gradle/incremental.annotation.processors
下进行声明你属于那种增量处理器.
当然有时候我们只能在运行时确定自己是哪种增量处理器,或者动态开启增量,这种类别的我们称为dynamic
.它同样需要resources/META-INF/gradle/incremental.annotation.processors
下进行声明.
resources/META-INF/gradle/incremental.annotation.processors
声明的语法如下:
注解处理器的全限定名,类别
注解处理器的全限定名2,类别
如:
org.gradle.EntityProcessor,isolating
org.gradle.EntityProcessor,aggregating
org.gradle.ServiceRegistryProcessor,dynamic
不管是哪种他们都有如下的限制:
-
只能使用
Filer API
生成新的文件 -
不能依赖于特定编译器
API
,比如说com.sun.source.util.Trees.
(这个api一般用于修改输入的文件,在实际情况下我们都是新生成一个文件,而不是修改.可以参阅博主其他文章:Java Pluginable Annotation processing) -
如果你使用了
Filer#createResource
这个api.(用于生成新的资源文件,比如xxx.xml,其中有一个参数location
用确定新生成的文件放入在哪里),那么你的location
参数仅能是CLASS_OUTPUT, SOURCE_OUTPUT, NATIVE_HEADER_OUTPUT.
在大多数情况我们都只会用这几个位置,除非你有特殊要求
class Filer{
FileObject createResource(JavaFileManager.Location location,
CharSequence pkg,
CharSequence relativeName,
Element... originatingElements)
}
这里我相信有很多人看完一脸懵逼,大多数原因如下:
- 很多人直接使用
javapoet
库所以对Filer
知道的并不多.(需要读者自行翻阅下doc文档,大致对api有一个了解) - 没使用过apt修改过现有文件(比如生成set和get方法),而只是新建一个文件.(可参阅:Java Pluginable Annotation processing)
- 没使用apt生成过资源文件.(比较简单,也比较少用)
下面是一个直接使用Filer
生成一个新的类的案例,希望对大家有帮助
class MyAnnotationCompiler : AbstractProcessor() {
private var messager: Messager? = null
private var elementUtils: Elements? = null
private var typeUtils: Types? = null
private var filer: Filer? = null
override fun init(processor: ProcessingEnvironment) {
super.init(processor)
messager = processor.messager
elementUtils = processor.elementUtils
typeUtils = processor.typeUtils
filer = processor.filer
}
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return false
}
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
val className = """${element.simpleName.toString()}Hello"""
val createSourceFile =
//注意createSourceFile有两个参数,后面一个是可选参数,我们这里不传
//后面一个参数可用于增量构建,后文讲解
filer?.createSourceFile(
"$packageString.$className"
)
val writer = createSourceFile?.openWriter()
writer?.apply {
write("package ${packageElement};")
write("\\n")
write("class ${className}{}")
close()
}
}
}
}
isolating注解处理器
相对aggregating
,dynamic
这是最快的一种增量注解处理器,也是最容易理解的一个.
我们看下的这个类别特有的限制:
限制1. isolating
要求每一个注解处理器
仅能使用一个相关的编译时注解
去生成新的文件,简单来说你不能用两个以上的注解去生成一个新文件
//注解 1
@MyAnnotation()
public class MyFour {
int i1 =2;
}
//注解 2
@MyAnnotation2
class Test{
}
下面使用了两个注解生成一个文件,这种情况下是不能注册为isolating
注解处理器.如果你需要这种情况请使用aggregating
类别.
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return false
}
//使用注解1
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
//使用注解2
val elementsAnnotated2 =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation2::class.java).first()
val secondName2 = elementsAnnotated2.simpleName
//结合两个注解一起生成类
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
//使用两个注解一起生成类文件夹名
val className = """${element.simpleName.toString()}Hello${secondName2}"""
val createSourceFile =
filer?.createSourceFile(
"$packageString.$className",
)
val writer = createSourceFile?.openWriter()
writer?.apply {
write("package ${packageElement};")
write("\\n")
write("class ${className}{}")
close()
}
}
return true
}
override fun init(processor: ProcessingEnvironment) {
super.init(processor)
messager = processor.messager
elementUtils = processor.elementUtils
typeUtils = processor.typeUtils
filer = processor.filer
}
}
- 限制2. 对于每个用
filer
生成的文件,必须要在构造时传入一个originating element
与其对应.
下面便是一个完全符合isolating
规范的注解处理器.
添加一个声明到resources/META-INF/gradle/incremental.annotation.processors
:
#incremental.annotation.processors
com.example.annotationcompiler.MyAnnotationCompiler,isolating
class MyAnnotationCompiler : AbstractProcessor() {
private var messager: Messager? = null
private var elementUtils: Elements? = null
private var typeUtils: Types? = null
private var filer: Filer? = null
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return false
}
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
val className = """${element.simpleName.toString()}Hello"""
val createSourceFile =
//注意看第二个参数传入了一个element,并且只能传入一个
filer?.createSourceFile(
"$packageString.$className",
element
)
val writer = createSourceFile?.openWriter()
writer?.apply {
write("package ${packageElement};")
write("\\n")
write("class ${className}{}")
close()
}
}
return true
}
override fun init(processor: ProcessingEnvironment) {
super.init(processor)
messager = processor.messager
elementUtils = processor.elementUtils
typeUtils = processor.typeUtils
filer = processor.filer
}
}
对于使用javapoet的同学可以看下面这个案例:
class MyAnnotationCompiler : AbstractProcessor() {
private var messager: Messager? = null
private var elementUtils: Elements? = null
private var typeUtils: Types? = null
private var filer: Filer? = null
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return false
}
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
val className = """${element.simpleName.toString()}Hello"""
val main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC)
.returns(Void.TYPE)
.addParameter(Array<String>::class.java, "args")
.addStatement("\\$T.out.println(\\$S)", System::class.java, "Hello, JavaPoet!")
.build()
val helloWorld = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
//传入一个原始的element 才能变成增量
.addOriginatingElement(element)
.build()
val javaFile = JavaFile.builder(packageString, helloWorld)
.build()
javaFile.writeTo(filer)
}
return true
}
}
aggregating注解处理器
我们看到上面有一个问题isolating
,不能使用多个注解生成一个文件,而aggregating
是允许的.但是增量成功率和效率比不上isolating
.
aggregating
限制如下:
- 限制1:注解的Retention必须是
CLASS
orRUNTIME
@Target({ElementType.TYPE})
//这是ok的
@Retention(RetentionPolicy.CLASS)
public @interface MyT {
}
@Target({ElementType.TYPE})
//可以的
@Retention(RetentionPolicy.RUNTIME)
public @interface MyT {
}
@Target({ElementType.TYPE})
//错误的
@Retention(RetentionPolicy.SOURCE)
public @interface MyT {
}
- 限制2:如果用户传递-parameters编译器参数,则它们只能读取参数名称。(不理解,估计也不常用)
我们看一个合格的一个Demo
@Target(allowedTargets = [AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION])
@Retention(AnnotationRetention.BINARY)//等价class
annotation class MyAnnotation(val value: Int = 20, val value2: Int = 40) {
}
@Target(allowedTargets = [AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION])
@Retention(AnnotationRetention.BINARY)//等价class
annotation class MyAnnotation2(val value: Int = 20, val value2: Int = 40) {
}
添加一个声明到resources/META-INF/gradle/incremental.annotation.processors
:
#incremental.annotation.processors
com.example.annotationcompiler.MyAnnotationCompiler,aggregating
class MyAnnotationCompiler : AbstractProcessor() {
private var messager: Messager? = null
private var elementUtils: Elements? = null
private var typeUtils: Types? = null
private var filer: Filer? = null
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return false
}
//注解1
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
//注解2
val elementsAnnotated2 =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation2::class.java).first()
val secondName2 = elementsAnnotated2.simpleName
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
val className = """${element.simpleName.toString()}Hello${secondName2}"""
val createSourceFile =
filer?.createSourceFile(
"$packageString.$className"
)
val writer = createSourceFile?.openWriter()
writer?.apply {
write("package ${packageElement};")
write("\\n")
write("class ${className}{}")
close()
}
}
return true
}
override fun init(processor: ProcessingEnvironment) {
super.init(processor)
messager = processor.messager
elementUtils = processor.elementUtils
typeUtils = processor.typeUtils
filer = processor.filer
}
}
dynamic注解处理器
假设我们有一个需求,需要用户自己决定是否开启增量注解,那么dynamic
就很适合.
kapt {
useBuildCache = false
arguments {
//将这个选项传递到注解处理器中
arg("enableIncremental", "true")
}
}
dynamic
要求我们重写AbstractProcessor.getSupportedOptions
函数,然后根据自身情况返回是哪一种增量注解处理器类型.
直接看一个Demo:
class MyAnnotationCompiler : AbstractProcessor() {
private var messager: Messager? = null
private var elementUtils: Elements? = null
private var typeUtils: Types? = null
private var filer: Filer? = null
override fun process(
set: MutableSet<out TypeElement>,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()||set.size!=2) {
return false
}
val elementsAnnotatedWith =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation::class.java)
val elementsAnnotated2 =
roundEnvironment.getElementsAnnotatedWith(MyAnnotation2::class.java).first()
val secondName2 = elementsAnnotated2.simpleName
for (element in elementsAnnotatedWith) {
val packageElement = elementUtils?.getPackageOf(element)!!
val packageString = packageElement.qualifiedName.toString()
val className = """${element.simpleName.toString()}Hello${secondName2}"""
val createSourceFile =
filer?.createSourceFile(
"$packageString.$className"
)
val writer = createSourceFile?.openWriter()
writer?.apply {
write("package ${packageElement};")
write("\\n")
write("class ${className}{}")
close()
}
}
return true
}
val enableIncrementalOption = "enableIncremental"
override fun init(processor: ProcessingEnvironment) {
super.init(processor)
messager = processor.messager
elementUtils = processor.elementUtils
typeUtils = processor.typeUtils
filer = processor.filer
//init优先调用,然后才调用getSupportedOptions
val enableIncremental = processor.options[enableIncrementalOption]
if ("true" == enableIncremental) {
//根据你的注解处理器选择是哪种
set.add("org.gradle.annotation.processing.aggregating")
// set.add("org.gradle.annotation.processing.isolating")
}
}
var set = mutableSetOf<String>(enableIncrementalOption)
override fun getSupportedOptions(): MutableSet<String> {
return set
}
}
小技巧
在你的gradle.properties中加入以下属性:
//输出katp相关信息
kapt.verbose=true
#利用jdwp调试注解处理器
org.gradle.jvmargs= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
另外gradle自身有一个小bug
,当你发现你的增量失效时,去删除使用处理器的build
文件夹.
于是本文提了一个issue给gradle官方gradle issue
以上是关于gradle编译时注解增量教程的主要内容,如果未能解决你的问题,请参考以下文章
自定义Gradle plugin Java AnnotationProcessor 和 Kotlin Kapt 断点调试