一、什么是 APT
APT 的全称是 Annotation Processing Tool 注解处理器。可以将注解在编译期翻译成 Java代码
二、如何使用 APT
1.创建注解库
新建一个 module 放置注解 比如 annotation module,里面还有 ARouter 注解
- 注意一定要是一个 java module
- 将注解单独放在一个库中,因为这个库不仅要被 APP 库引用,还需要被注解处理器库使用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter{
String path();
}
三、创建一个注解处理器库
1、创建一个专门用来处理注解的 Java Module。假设叫做 APT。在这个库的 build.gradle 下引入
// 这是 google 提供的注解处理器库
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service1.0-rc4'
// 这是 square 公司提供的生成 Java 源代码的库
implementation 'com.squareup:javapoet:1.9.0'
// 这里引入我们自己的注解库
implementation project(':annotation')
// 解决中文乱码问题
task.withType(JavaCompile){
options.encoding = "UTF-8"
}
2、创建一个注解处理器类继承 google 提供的 AbstractProcessor 类,并且在类上添加一些注解
1.@AutoService 声明这个类是一个注解处理器
2.@SupportedAnnotationTypes 表示关注的注解类
3.SupportedSourceVersion 表示生成的 Java 源代码的版本
4.@SupportedOptions 表示在编译时关注的 options,可以在编译时传递参数
参数 | 作用 |
---|---|
Elements | 查询元素节点的信息的工具 |
Messager | 日志工具类,在编译时打印日志 |
Filer | 文件生成器,生成 Java 代码时需要被用到 |
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.baidu.crazyorange.annotation.ARouter"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {
// 元素节点
private Elements elementUtils;
private Types typesUtils;
// 输出日志
private Messager messager;
// 文件生成器
private Filer filer;
/**
* 初始化
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
// 初始化信息
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typesUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
String content = processingEnvironment.getOptions().get("content");
messager.printMessage(Diagnostic.Kind.NOTE, "从app module 中获取到的参数: " + content);
}
.....
在引入编译插件的地方加入javaCompileOptions,可以在编译时给注解解释器传入参数
注意必须在 defaultConfig 代码块下面
defaultConfig {
applicationId appId.appId
minSdkVersion androidCompile.minSdkVersion
targetSdkVersion androidCompile.targetSdkVersion
versionCode androidCompile.versionCode
versionName androidCompile.versionName
/**
* 编译时给 APT 传入一些参数
* 必须在defaultConfig节点下加入,否则会报错
*/
javaCompileOptions{
annotationProcessorOptions{
arguments = [content : 'hello apt']
}
}
}
process 函数式真正处理注解的地方,参数 set 返回使用了我们在 SupportedAnnotationTypes 注解的类。
注意如果没有类使用我们关注的注解,Process 方法将不被执行
- 通过 Elements(ElementsUtils) 获取到使用注解类的包名
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
- 通过 Element 获取到类名
String className = element.getSimpleName().toString();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) {
messager.printMessage(Diagnostic.Kind.NOTE, "没有人使用该注解: ");
return false;
}
// 查找被 ARouter 注解注释过的类
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
// 获取这个类节点的包节点信息 getQualifiedName 是全路径
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被被注解的包是: " + packageName);
messager.printMessage(Diagnostic.Kind.NOTE, "被被注解的类是: " + className);
// 最终我们要根据注解生成的类
String finalClassName = className + "$$ARouter";
.....
}
四、使用 JavaPoet 翻译成 Java 源代码
参数 | 作用 |
---|---|
MethodSpec | 构造一个方法体 |
FieldSpec | 代表构造一个字段 |
JavaFile | 文件生成器,生成 Java 代码时需要被用到 |
ParamterSpec | 创建参数 |
AnnotationSpec | 创建注解 |
ClassName | 通过Element拿到拿到类信息 |
/**
* 使用 javaPort 来生成类文件
*/
ARouter aRouter = element.getAnnotation(ARouter.class);
// 生成方法体
MethodSpec methodSpec = MethodSpec.methodBuilder("findTargetPath")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(String.class, "path")
// 构建方法体
.addStatement("return path.equals($S)? $T.class:null", aRouter.path(), ClassName.get((TypeElement) element))
.returns(Class.class)
.build();
// 构建类体
TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName).addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(methodSpec).build();
// 将类写入具体的包下面
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
javaFile.writeTo(filer);
五、整个工程结构
六、生成类的样子
package com.baidu.crazyorange.componenttest;
import java.lang.Class;
import java.lang.String;
public final class MainActivity$$ARouter {
public static Class findTargetPath(String path) {
return path.equals("/app/MainActivity")? MainActivity.class:null;
}
}
六、注意点
1.确保 AbstractProcess 注册成功,如果注册成功可以在 APT 的 build--classes--java--main--META-INF下有一个标签
2.确保自己关注的注解被其他类使用,如果没被使用,process 方法将不执行
3.通过在 init 方法中追加日志的方式,确保 init 正常
4.确保关注的注解路径是正确的
5.确保android module 引用了你的 apt module
annotationProcess project(':apt')