这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
APT(Annotation Processing Tool)
是一种处理注释的工具,它对源代码文件进行检测,找出其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能运行。简而言之:根据规则,生成代码或类文件。
文件结构
package com.loveqrc.aptdemo //PackageElement 包元素/节点
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() { //TypeElement 类元素/节点
val userName = "Rc在努力" //VariableElement 属性元素/节点
override fun onCreate(savedInstanceState: Bundle?) { //ExecutableElement 方法元素/节点
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
PackageElement表示一个包程序元素。提供对有关包及其成员的信息的访问。ExecutableElement表示某个类或接口的方法、构造方法或初始化程序(静态或实例)。TypeElement表示一个类或接口程序元素。提供对有关类型及其成员的信息访问。VariableElement表示一个字段、enum常量、方法或构造方法参数、局部变量。
常用API
| 方法名 | 方法解析 |
|---|---|
| getEnclosedElements() | 返回该元素直接包含的子元素 |
| getEnclosingElement() | 返回包含该element的父element,与getEnclosedElements() 效果相反 |
| getSimpleName() | 获取名字,不带包名 |
| getQualifiedName() | 获取全名,如果是类的话,包含完整的包名路径 |
创建Annotaion
新建java lib ,用于存放Annotaion
创建ARouter注解
@Target(AnnotationTarget.CLASS) // 该注解作用在类之上
@kotlin.annotation.Retention(AnnotationRetention.BINARY) // 要在编译时进行一些预处理操作。注解会在class文件中存在
annotation class ARouter(
// 详细路由路径(必填),如:"/app/MainActivity"
val path: String,
// 路由组名(选填,如果开发者不填写,可以从path中截取出来)
val group: String = ""
)
target表明注解在哪个位置
@Target(ElementType.TYPE)// 接口、类、枚举、注解@Target(ElementType.FIELD)// 属性、枚举的常量@Target(ElementType.METHOD)// 方法@Target(ElementType.PARAMETER)// 方法参数@Target(ElementType.CONSTRUCTOR)// 构造函数@Target(ElementType.LOCAL_VARIABLE)// 局部变量@Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上@Target(ElementType.PACKAGE)// 包
Retention表示注解的生命周期
生命周期:SOURCE < CLASS(BINARY) < RUNTIME
1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解,会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
创建注解处理器
新建java lib ,用于处理Annotaion。
添加依赖
plugins {
id 'java-library'
id 'kotlin'
id 'kotlin-kapt'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//注解处理器
kapt 'com.google.auto.service:auto-service:1.0-rc6'
api 'com.google.auto.service:auto-service:1.0-rc6'
// 引入annotation,让注解处理器-处理注解
implementation project(':annotation')
}
// java控制台输出中文乱码,但是kotlin并不生效......
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
创建ARouterProcessor集成AbstractProcessor
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor::class)
// 允许/支持的注解类型,让注解处理器处理(新增annotation module)
@SupportedAnnotationTypes("com.loveqrc.annotation.ARouter")
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions("content")
class ARouterProcessor : AbstractProcessor() {
// 操作Element工具类 (类、函数、属性都是Element)
lateinit var elementUtils: Elements
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
lateinit var typeUtils: Types
// Messager用来报告错误,警告和其他提示信息
lateinit var messager: Messager
// 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
lateinit var filer: Filer
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
elementUtils = processingEnv.elementUtils;
messager = processingEnv.messager;
filer = processingEnv.filer;
messager.printMessage(Diagnostic.Kind.NOTE, "Rc woooo");
val content: String? = processingEnv.options["content"]
messager.printMessage(Diagnostic.Kind.NOTE, content);
}
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param annotations
使用了支持处理注解的节点集合(类 上面写了注解),因为注解处理器可以处理多个注解,所以个这里返回的时集合
* @param roundEnv 当前或是之前的运行环境,可以通过该对象查找找到的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
override fun process(
annotations: MutableSet<out TypeElement>,
roundEnv: RoundEnvironment
): Boolean {
if (annotations.isEmpty()) return false;
// 获取所有带ARouter注解的 类节点
val elements: Set<Element> = roundEnv.getElementsAnnotatedWith(
ARouter::class.java
)
elements.forEach {
//获取包名
val packageName = elementUtils.getPackageOf(it).qualifiedName.toString();
messager.printMessage(Diagnostic.Kind.NOTE, packageName);
// 获取简单类名
val className: String = it.simpleName.toString()
//被注解的类
messager.printMessage(Diagnostic.Kind.NOTE, className);
// 定义最终想生成的类文件名
val finalClassName = className +"ARouter"
try {
// 创建一个新的源文件(Class),并返回一个对象以允许写入它
val sourceFile = filer.createSourceFile("$packageName.$finalClassName")
// 定义Writer对象,开启写入
val writer: Writer = sourceFile.openWriter()
// 设置包名
writer.write("package $packageName;\n")
writer.write("public class $finalClassName {\n")
writer.write("public static Class<?> findTargetClass(String path) {\n")
// 获取类之上@ARouter注解的path值
val aRouter: ARouter = it.getAnnotation(ARouter::class.java)
writer.write(
"""
if (path.equals("${aRouter.path}")) {
""".trimIndent()
)
writer.write("return $className.class;\n}\n")
writer.write("return null;\n")
writer.write("}\n}")
// 最后关闭流
writer.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return true
}
}
首先使用@AutoService注册ARouterProcessor,类似Activity要在manifest中注册一样,然后绑定它要处理的注解,一个注解处理器可以处理多个不同的注解,这就是为什么ARouterProcessor的process会返回MutableSet<out TypeElement>原因,随后绑定编译的版本和接受的参数。传入的参数在引入的工程中配置,如下所示
android {
defaultConfig {
kapt {
arguments {
arg("content", project.getName())
}
}
}
}
使用注解处理器
创建注解和注解处理器之后,就可以检验一下是否正确了,首先添加依赖
// 依赖注解
implementation project(':annotation')
// 依赖注解处理器
kapt project(':compiler')
然后在Activity中添加注解
@ARouter(path = "app/MainActivity")
class MainActivity : AppCompatActivity() {
}
然后重新编译,在build文件夹下能看到注解处理器生成的文件
生成的文件时这样的
package com.loveqrc.aptdemo;
public class MainActivityARouter {
public static Class<?> findTargetClass(String path) {
if (path.equals("app/MainActivity")) {
return MainActivity.class;
}
return null;
}
}
生成文件后,就能通过MainActivityARouter查找对应的class了。从上面可以知道
通过APT生成想要的文件,能减少开发工作量,利用的好能大大提供开发效率。