APT,即注解处理器,是一种处理注解的工具。确切来说,它是javac的一个工具,用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,以生成.java文件作为输出。简单来说,就是在编译期通过注解生成.java文件。
自定义注解处理器,需要继承AbstractProcessor类。对于AbstractProcessor来说,最重要的就是process方法,process方法处理的核心是Element对象。
下面我们详细看下Element对象。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.lang.model.element;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.type.TypeMirror;
public interface Element extends AnnotatedConstruct {
TypeMirror asType();//返回元素的类型,实际的对象类型。
ElementKind getKind();//返回此元素的种类(即5种子类):包、类、接口、方法、字段等
Set<Modifier> getModifiers();//返回此元素的修饰符,如public、static、final等关键字
Name getSimpleName();//返回此元素的简单名称,如类名
Element getEnclosingElement();
//返回该元素的父元素;ExecutableElement的父级是TypeElemnt,而TypeElemnt的父级是PackageElment。
//注意:包元素getEnclosingElement()返回是null
List<? extends Element> getEnclosedElements();
//返回该元素所包含的元素,相当于在当前元素向里解一层。比如当前元素类型是class,getEnclosingElements()可以获取该类所包含的所有成员变量和方法。当前是包元素是,返回该包下的所有类元素。
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
//返回此元素针对指定类型的注解。注解可以是继承的,也可以是直接存在于此元素上的。
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
除了上述的几个方法外,APT中还有以下几个方法比较常用
属性名 | 含义 |
---|---|
getSimpleName() | 获取名字,如果是类元素,不包含完整的包名路径 |
getQualifiedName() | 获取全名,如果是类的话,包含完整的包名路径 |
getParameters() | 获取方法的参数元素,每个元素是一个VariableElement |
getReturnType() | 获取方法元素的返回值 |
getConstantValue() | 如果属性变量被final修饰,则可以使用该方法获取它的值 |
Element有5个直接子类,它们分别代表一种特定类型的元素。
类型 | 含义 |
---|---|
TypeElement | 一个类或接口程序元素 |
VariableElement | 一个字段、enum常量、方法或构造方法参数、局部变量或异常参数 |
ExecutableElement | 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素 |
PackageElement | 一个包程序元素 |
TypeParameterElement | 一般类、接口、方法或构造方法元素的泛型参数 |
也许,这么说太过于官方了,我们举个简单的例子,大家一看就明白
package com.netease.apt; //PackageElement 包元素
public class Main{//TypeElement 类元素
private int x: //VariableElement 属性元素
private Main{}// ExecutableElement 方法元素
}
其中,TypeElement和VariableElement是最核心的两个Element。
接下来,我们通过APT来实现一个类似于ButterKnife中的@BindView注解。通过对View变量的注解,实现对View的绑定。
首先,我们新建一个java依赖库,专门来存放我们的自定义注解。
接下来,我们创建一个apt-processor模块(也是一个java模块),同时该模块要依赖于apt-annotation模块以及auto-service第三方库。
BrettBindViewProcesor继承AbstractProcessor类,并且需要在类上使用@AutoService(Processor.class)注解标记,表明当前类是一个注解处理器。
接着需要我们创建文件src/main/resources/META-INF/services/javax.annotation.processing.Processor进行声明。文件里的内容就是我们自定义的注解处理器的包名和类名。在本例中如下所示:
com.brett.apt_processor.BrettBindViewProcesor
AbstractProcessor类有几个重要的方法如下所示:
方法名 | 含义 |
---|---|
init | 初始化函数,可以得到ProcessingEnvironment对象。ProcessingEnvironment提供很多有用的工具类,如Elements、Types和Filer。 |
getSupportedAnnotationTypes | 指定这个注解处理器是注册给哪个注解的,这里指定的是我们上面创建的注解@BrettBindView。 |
getSupportedSourceVersion | 指定使用的Java版本,通常这里返回SourceVersion.latestSupported()。 |
package com.brett.apt_processor;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
/**
* Created by Brett.li on 2022/7/3.
*/
public class BrettClassCreateFactory {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
BrettClassCreateFactory(Elements elements, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elements.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mBindingClassName = className + "_BrettViewBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("package ").append(mPackageName).append(";\n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateBindViewMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
private void generateBindViewMethods(StringBuilder builder) {
builder.append("\tpublic void bindView(");
builder.append(mTypeElement.getQualifiedName());
builder.append(" owner ) {\n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String viewName = element.getSimpleName().toString();
String viewType = element.asType().toString();
builder.append("\t\towner.");
builder.append(viewName);
builder.append(" = ");
builder.append("(");
builder.append(viewType);
builder.append(")(((android.app.Activity)owner).findViewById( ");
builder.append(id);
builder.append("));\n");
}
builder.append(" }\n");
}
public String getProxyClassFullName() {
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement(){
return mTypeElement;
}
}
package com.brett.apt_processor;
import com.brett.apt_annotation.BrettBindView;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;
@AutoService(Processor.class)
//@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则不用复写getSupportedSourceVersion方法
//@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"}) //若使用了该注解,则不用复写getSupportedAnnotationTypes方法
public class BrettBindViewProcesor extends AbstractProcessor {
private Elements mElementUtils;
private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BrettBindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mClassCreateFactoryMap.clear();
//getElementsAnnotatedWith可以搜索到整个工程中所有使用了 BrettBindView 注解的元素
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BrettBindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;//因为BrettBindView是作用在字段中,故可以强转为VariableElement
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();//拿到父element,VariableElement的父element为TypeElement
String fullClassName = classElement.getQualifiedName().toString();
BrettClassCreateFactory proxy = mClassCreateFactoryMap.get(fullClassName);
if (proxy == null) {
proxy = new BrettClassCreateFactory(mElementUtils, classElement);
mClassCreateFactoryMap.put(fullClassName, proxy);
}
BrettBindView bindAnnotation = variableElement.getAnnotation(BrettBindView.class);//拿到被BrettBindView注解注释的控件
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}
//创建java文件
for (String key : mClassCreateFactoryMap.keySet()) {
BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);
try {
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
Writer writer = jfo.openWriter();
writer.write(proxyInfo.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;//返回true或false好像没有太大的区别
}
}
//apt-processor模块的gradle文件
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation 'com.google.auto.service:auto-service:1.0.1'
implementation project(':apt-annotation')
}
最后,我们在app模块中引入上面两个模块即可。
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation project(':apt-annotation')
annotationProcessor project(':apt-processor')
}
package com.brett.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.brett.apt_annotation.BrettBindView;
import com.brett.test1.Test1Activity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
@BrettBindView(R.id.btn_main)
Button btnMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// btnMain = findViewById(R.id.btn_main);
bindView(this);
btnMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, Test1Activity.class);
startActivity(intent);
}
});
}
public static void bindView(Activity activity){
Class clazz = activity.getClass();
try {
Class<?> bindViewClass = Class.forName(clazz.getName()+"_BrettViewBinding");
Method method = bindViewClass.getMethod("bindView",activity.getClass());
method.invoke(bindViewClass.newInstance(),activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
注意:编译时可能会报异常,可能是autoService跟java版本不匹配导致的,需要调试一下。
当我们build之后,如果在build文件夹看到如下文件说明创建成功
在上面的例子中,我们通过StringBuilder拼接生成了对应的java代码。但是,这种做法比较繁琐、容易出错,而且难以维护。所以,我们可以使用javapoet库来生成Java代码,javapoet引入了oop的思想。
注意:使用javapoet生成代码未必就比使用原生方式好,如果复杂的代码生成,反而效率会低下。
javapoet是一个开源项目,GitHub地址:https://github.com/square/javapoet
我们在BrettClassCreateFactory类中添加如下代码:
//使用javapoet创建java代码
public TypeSpec generateJavaCodeWithJavapoet(){
return TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodsWithJavapoet())
.build();
}
private MethodSpec generateMethodsWithJavapoet(){
ClassName owner = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(owner,"owner");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String viewName = element.getSimpleName().toString();
String viewType = element.asType().toString();
methodBuilder.addCode("owner." + viewName + " = " + "(" + viewType + ")(((android.app.Activity)owner).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
package com.brett.apt_processor;
import com.brett.apt_annotation.BrettBindView;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.JavaFileObject;
@AutoService(Processor.class)
//@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则可以不用复写getSupportedSourceVersion方法
//@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"}) //若使用了该注解,则可以不用复写getSupportedAnnotationTypes方法
public class BrettBindViewProcesor extends AbstractProcessor {
private Elements mElementUtils;
private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BrettBindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//。。。。。
//创建java文件
for (String key : mClassCreateFactoryMap.keySet()) {
BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);
//使用原始方式
// try {
// JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
// Writer writer = jfo.openWriter();
// writer.write(proxyInfo.generateJavaCode());
// writer.flush();
// writer.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//使用javapoet方式
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCodeWithJavapoet()).build();
try{
javaFile.writeTo(processingEnv.getFiler());
}catch (Exception e){
e.printStackTrace();
}
}
return true;//返回true或false好像没有太大的区别
}
}
JavaPoet相关
类对象 | 说明 |
---|---|
MethodSpec | 代表一个构造函数或方法声明 |
TypeSpec | 代表一个类,接口或枚举声明 |
FieldSpec | 代表一个成员变量、字段声明 |
JavaFile | 代表一个顶级类的java文件 |
Parameterspec | 用来创建参数 |
AnnotationSpec | 用来创建注解 |
ClassName | 用来包装一个类 |
TypeName | 类型,如在添加返回值类型是使用 TypeName . VoID |
最后,我们build下工程同样可以生成MainActivity_BrettViewBinding文件。