介绍
APT就是(Annotation Processing Tool )的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。
代表框架:
Dagger2
ButterKnife
EventBus
ARouter
作用
使用APT的优点就是方便、简单,可以少些很多重复的代码。
APT处理要素
注册处理器(AutoService) + 注解处理器(AbstractProcessor) + 代码生成(javapoet)
1、 注册处理器
注册处理器有2种方式
一种是AutoService
首先引入
implementation 'com.google.auto.service:auto-service:1.0-rc2'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2'
然后再AbstractProcessor类中加上注解@AutoService(Processor.class)
这样就注册完成了
第二种是添加文件
。
2、 注解处理器(AbstractProcessor)
创建一个继承AbstractProcessor 类就可以了,在process方法里,获取我们需要的哪些注解。
public class BindViewProcessor extends AbstractProcessor {
public class BindViewProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
/**
* 指定注解
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
/**
* 用来指定你使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 扫描注解处理
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return true;
}
3、 代码生成
代码生成也有2种方式来实现
1.StringBuilder
来实现,大概例子是这样的,所有的代码都是用字符串拼接起来,生成的代码格式也很乱,而且很容易写错。
2.另外一种是使用javapoet
生成代码,生成的代码会自动排版,javapoet详细用法
可以看下两者的比较。
/**
* 使用StringBuilder创建类、而且还需要自己手写导入包,这边没写
*/
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("package ").append(mPackageName).append(";\n\n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateMethods(builder);//创建方法
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
/**
* 使用javapoet创建类
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())//创建方法
.build();
return bindingClass;
}
/**
* 使用StringBuilder创建方法
*/
private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " target ) {\n");
for (int id : mapId.keySet()) {
VariableElement element = mapId.get(id);
String name = element.getSimpleName().toString();
builder.append("target." + name).append(" = ");
builder.append("target.findViewById( " + id + ");\n");
}
builder.append(" }\n");
}
/**
* 使用javapoet创建方法
*/
private MethodSpec generateMethods2() {
ClassName target = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(target, "target");
for (int id : mapId.keySet()) {
VariableElement element = mapId.get(id);
String name = element.getSimpleName().toString();
methodBuilder.addCode("target." + name + " = " + "target.findViewById( " + id + ");");
}
return methodBuilder.build();
}
这三步完成我们就可以生成我们要的代码了。
javapoet详细用法
获取注解对象
1、运行时注解: 通过 反射 机制获取注解对象,会损耗性能,常用的框架有retrofit
2、编译期注解: 通过 APT 方式获取注解对象,不会造成性能损耗。常用的框架Dagger2, ButterKnife、EventBus、ARouter
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
}
@Retention(RetentionPolicy.CLASS)
:表示编译时注解
@Target(ElementType.FIELD)
:表示注解范围为类成员(构造方法、方法、成员变量)
注解 @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) 包
element 的概念
表示一个程序元素,比如包、类或者方法。
element 包含有
PackageElement
表示一个包程序元素
TypeElement
表示一个类或接口程序元素
VariableElement
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement
表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement
表示一般类、接口、方法或构造方法元素的泛型参数
如下图
public class Foo { // TypeElement 类型元素
private int a; // VariableElement 变量元素
private Foo other; // VariableElement 变量元素
public Foo() { // ExecutableElement 可执行元素
}
public void setA(int newA ) { // newA 代表是一个 VariableElement)
}
}
Element
的源码,源码解析。
public interface Element extends javax.lang.model.AnnotatedConstruct {
/**
* 返回该元素定义的类型。
* 泛型元素定义了一系列类型,而不仅仅是一个类型。如果这是一个泛型元素,则返回一个原型
* 类型。这是元素在对应于它自己的正式类型参数的类型变量上的调用。例如,对于泛型类元素
* C<N extends Number>,返回参数化类型C<N>。类型实用程序接口有更一般的方法来获取元
* 素定义的所有类型的范围。
*/
TypeMirror asType();
/**
* 返回该元素的类型
*/
ElementKind getKind();
/**
* 返回该元素的修饰符,包括注解.
* 隐式修饰符也包含,比如接口方法中的public和static
*/
Set<Modifier> getModifiers();
/**
* 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。
* 举例,java.util.Set<E>的简单名称是Set.
* 如果该元素代表的是未命名包,则返回一个空 Name.
* 如果代表的是构造器,则返回<init>所对应的Name.如果代表的是静态代码块,则返回的是<clinit>
* 如果代表的是匿名类或者是初始代码块,则返回一个空 Name.
*/
Name getSimpleName();
/**
* 返回包围该元素的最内层的元素.
* 如果这个元素的声明紧接在另一个元素的声明中,则返回另一个元素。
* 如果这是顶级类型,则返回其包。
* 如果这是一个包,则返回null。
* 如果这是类型参数,则返回类型参数的泛型元素。
* 如果这是一个方法或构造函数参数,则返回声明该参数的可执行元素。
*/
Element getEnclosingElement();
/**
* 返回该元素所包含的元素.
* 类或接口被认为包含了它直接声明的字段、方法、构造函数和成员类型.包直接包含了顶级类和接
* 口,但不包含其子包。其他类型的元素目前不被认为包含任何元素;然而,随着这个API或编程语
* 言的发展,这些元素可能会改变
*/
List<? extends Element> getEnclosedElements();
/**
* 当给定的参数和当前类代表同一个元素时返回true,否则,返回false.
* 注意,元素的标识涉及不能直接从元素的方法中访问的隐式状态,包括关于不相关类型的存在的
* 状态。即使“同一个”元素正在被建模,由这些接口的不同实现创建的元素对象也不应该期望相
* 等;这类似于通过不同的类加载器加载的同一个类文件的Class对象是不同的
*
*/
@Override
boolean equals(Object obj);
/**
* 基于Object.hashCode
*/
@Override
int hashCode();
/**
* 获得直接声明在该元素上的注解
* 如果要获得继承的注解,使用Elements#getAllAnnotationMirrors(Element)方法.
*/
@Override
List<? extends AnnotationMirror> getAnnotationMirrors();
@Override
<A extends Annotation> A getAnnotation(Class<A> annotationType);
<R, P> R accept(ElementVisitor<R, P> v, P p);
}
网友评论