前面我们已经讲过注解的基本知识,对于注解还不太了解的,可以去看一下之前的文章,
android 注解详解_袁震的博客-CSDN博客。
之前我们在讲注解的时候,提到过APT和JavaPoet,那么什么是APT和JavaPoet呢?下面我们来详细讲解一下。
APT,英文全称Annotation Processing Tool,是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码, 如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。注意,是在编译期间。
简单来讲,就是根据我们定义的注释规则,帮助我们生成代码,生成类文件。
在APT中,它会分为包元素,类元素,属性元素,方法元素。那么,这些元素的意义是什么呢?
首先,我们需要明白APT真正的作用是什么。就拿ButterKnife来说,他真正要实现的就是我们通过BindView,把id传给注解,然后就会在编译时动态生成很多类,专门去处理你绑定的这些id,从而达到你只需要几行代码就能实现绑定,点击事件等功能。那为什么不能直接就写好类去处理,而非要到编译期去自动生成类处理呢?因为它不知道你会传哪些id,所以需要动态的生成。
所以,我认为APT的主要作用就是帮你动态生成类。
import androidx.appcompat.app.AppCompatActivity;//PackageElement 包元素/节点
public class MainActivity2 extends AppCompatActivity { // TypeElement 类元素/节点
private int a;// VariableElement 属性元素/节点
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {// ExecuteableElement 方法元素/节点
super.onCreate(savedInstanceState);
}
}
这些元素的意义就是,它们会提供相关信息,来帮助你后面生成类。
包元素 |
PackageElement
|
表示一个包程序元素。提供对有关包及其成员的信息的访问
|
方法元素 |
ExecutableElement
|
表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
|
类元素 |
TypeElement
|
表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
|
属性元素 |
VariableElement
|
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
|
在AbstractProcessor中,有两个方法是核心方法:
//初始化工作,主要做一些准备工作
public synchronized void init(ProcessingEnvironment processingEnv) {
}
//处理注解 核心方法
//annotations 使用了支持处理注解的节点集合
//roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解
//return true 表示后续处理器不会再处理(已经处理完成)
public abstract boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv);
上面ProcessingEnvironment中常用的api如下:
getElementUtils() | 获取操作Element的工具类 |
getMessager() | 获取Messager,用来打印日志相关信息 |
getFiler() | 获取文件生成器,类等最终要生成的文件,都是通过生成器生成的 |
getTypeUtils() | 获取类信息的工具类,用于操作TypeMirror的工具方法 |
getOptions() | 主要用来接收应用传过来的数据 |
上面RoundEnvironment常用api如下:
//获取所有被@YuanZhen注解的元素集合
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);
Element常用api如下:
getEnclosedElements()
|
返回该元素直接包含的子元素
|
getEnclosingElement()
|
返回包含该element的父element,与上一个方法相反
|
getKind()
|
返回element的类型,判断是哪种element
|
getModifiers()
|
获取修饰关键字,入public static final等关键字
|
getSimpleName()
|
获取名字,不带包名
|
getQualifiedName()
|
获取全名,如果是类的话,包含完整的包名路径
|
getParameters()
|
获取方法的参数元素,每个元素是一个VariableElement
|
getReturnType()
|
获取方法元素的返回值
|
getConstantValue()
|
如果属性变量被final修饰,则可以使用该方法获取它的值
|
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
新建MyAnnotationProcessor类,继承AnnotationProcessor
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
@Target(ElementType.TYPE) //作用与类上
@Retention(RetentionPolicy.SOURCE) //在编译时期生效
public @interface YuanZhen {
String value();//一个默认值
}
在compiler中依赖yuanzhenannotation
在app中添加依赖:
@YuanZhen("study")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
至此,APT环境搭建完成
在MyAnnotationProcessor类中:
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {
private Messager messager;// 用来打印日志相关信息
private Elements elementUtils;// 操作Element的工具类(类,函数,属性,其实都是Element)
private Filer filer;//文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
private Types typeUtils;// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
typeUtils = processingEnv.getTypeUtils();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
//因为javalib没有Log,所以我们使用messager来打印
messager.printMessage(Diagnostic.Kind.NOTE,"aaaaaaZZZZZ");//打印
//获取所有被@YuanZhen注解的元素集合
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);
for (Element element : elements) {
String className =element.getSimpleName().toString();//获取元素名
messager.printMessage(Diagnostic.Kind.NOTE,"-----="+className);//打印类名
YuanZhen annotation = element.getAnnotation(YuanZhen.class);//获取注解
messager.printMessage(Diagnostic.Kind.NOTE,"-----value="+annotation.value());//打印注解参数
}
return true;
}
}
具体的api在上文已经有个介绍,下面看Build日志输出:
参数study已经传递过来,类名MainActivity也已经获取到。
上面已经完成了APT环境的配置,下面就是动态生成类了,在javapoet之前,传统的生成类的方式就是采用字符串拼接的样式。最典型的应用就是EventBus。
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
// 通过注解处理的文件操作工具类创建源文件
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
// 截取包名和类名
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
// 以下就是写入生成的源文件中的代码
if (myPackage != null) {
writer.write("package " + myPackage + ";\n\n");
}
writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();\n\n");
// 写入订阅方法相关信息
writeIndexLines(writer, myPackage);
writer.write(" }\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
throw new RuntimeException("Could not write source for " + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}
通过上面可以看到,Eventbus是通过字符串拼接的形式来生成类的,这种方式虽然比较简单,但是也不是一件轻松的体力活,不符合OOP的编程思想。下面我们就来介绍一下现在最流行的写法,采用javapoet。
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法, 可以很方便的使用它根据注解生成对应代码。 通过这种自动化生成代码的方式, 可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
MethodSpec | 代表一个构造函数或方法声明 |
TypeSpec | 代表一个类,接口,或者枚举声明 |
FieldSpec | 代表一个成员变量,一个字段声明 |
JavaFile | 包含一个顶级类的Java文件 |
ParameterSpec | 用来创建参数 |
AnnotationSpec | 用来创建注解 |
ClassName | 用来包装一个类 |
TypeName | 类型,如在添加返回值类型是使用 TypeName.VOI |
$S 字符串,如:$S, ”hello $T 类、接口,如:$T, MainActivit |
传统模式生成类的思想是先写包,然后类,然后方法。
JavaPoet的思想恰恰相反,它是先方法,然后类,然后包。
在complier的build.gradle里面导包
implementation "com.squareup:javapoet:1.9.0"
/**
package com.yuanzhen.yuanzhenannotation;
public class MyClass {
public static void main(String[] args) {
System.out.println("Hello, yuanzhen");
}
}
*/
// 1.方法
MethodSpec mainMethod = MethodSpec.methodBuilder("main")//添加方法名
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)//添加修饰符
.returns(void.class)//添加返回值
.addParameter(String[].class, "args")//添加方法参数
.addStatement("$T.out.println($S)", System.class, "Hello, YuanZhen!")//添加内容
.build();
// 2.类
TypeSpec helloWorld = TypeSpec.classBuilder("MyClass1")//添加类名
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)//添加修饰符
.addMethod(mainMethod)//添加方法
.build();
// 3.包
JavaFile packagef = JavaFile.builder("com.yuanzhen.apt1", helloWorld).build();
// 去生成
try {
packagef.writeTo(filer);
messager.printMessage(Diagnostic.Kind.NOTE, "success...");
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "error...");
}
关于APT和JavaPoet的使用就讲完了,利用这个技术,我们可以实现很多强大的功能。