1、概念
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
简单来说就是在编译期,通过注解生成.java文件。
在Java注解中提到根据@Retention可以将注解分为三种,Java注解举的栗子都是运行期注解,通过反射应用,这必然带来性能问题,那么有没有更好的实现方式呢?既可以实现注入,还能保证性能无损耗呢?
当然有,就是编译时注解,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的,会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别。
2、作用
使用APT的优点就是方便、简单,可以少些很多重复的代码。
用过ButterKnife、ARouter、EventBus等注解框架的同学就能感受到,利用这些框架可以少些很多代码,只要写一些注解就可以了。
其实,他们不过是通过注解,生成了一些代码。
Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码。
3、示例
实现一个轻量的 “ButterKnife”,实现一个轻量的控件绑定框架,即通过注解来自动生成 findViewById() 代码。
3.1、创建项目
首先在工程中新建一个Java Library,命名为 apt_processor,用于存放 AbstractProcessor 的实现类。再新建一个 Java Library,命名为 apt_annotation ,用于存放各类注解。
当中,apt_processor 需要导入如下依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc6'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt_annotation')
}
Android Plugin for Gradle >= 3.4 或者 Gradle Version >=5.0 都要在自己的annotation processor工程里面增加如下的语句:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
当中,JavaPoet 是 square 开源的 Java 代码生成框架,可以很方便地通过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码。auto-service 是由 Google 开源的注解注册处理器。
实际上,上面两个依赖库并不是必须的,可以通过硬编码代码生成规则来替代,但还是建议使用这两个库,因为这样代码的可读性会更高,且能提高开发效率。
app Module 需要依赖这两个Library:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(':apt_annotation')
annotationProcessor project(':apt_processor')
}
这样子,我们需要的所有基础依赖关系就搭建好了:
3.2、自定义注解类
我们先声明一个注解,类似ButterKnife,BindView 注解的声明如下所示,放在 apt_annotation 中:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS):表示编译时注解
@Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)
注解值 value 用于声明 viewId
3.2、定义注解处理器
在 apt_processor Module 中创建 BindViewProcessor 类并继承 AbstractProcessor 抽象类,该抽象类含有一个抽象方法 process() 以及一个非抽象方法 getSupportedAnnotationTypes() 需要由我们来实现。如果找不到AbstractProcessor类,则需要检查一下自己创建的Module对不对,类型必须是Java Library。
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = "BindViewProcessor";
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
- init:初始化。可以得到ProcessingEnviroment,ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer;
- getSupportedAnnotationTypes:用于指定该 AbstractProcessor 的目标注解对象,这里说明是注解BindView;
- getSupportedSourceVersion:指定使用的Java版本,通常这里返回SourceVersion.latestSupported();
- process:用于处理包含指定注解对象的代码元素。可以在这里写扫描、评估和处理注解的代码,生成Java文件。
要自动生成 findViewById() 方法,则需要获取到控件变量的引用以及对应的 viewid,所以需要先遍历出每个 Activity 包含的所有注解对象。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有包含BindView注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因为 BindView 的作用对象是 FIELD,因此 element 可以直接转化为 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封装此 Element 的最里层元素
//如果 Element 直接封装在另一个元素的声明中,则返回该封装元素
//此处表示的即 Activity 类对象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//获取注解值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//将每个包含了 BindView 注解的字段对象以及其注解值保存起来
variableElementMap.put(viewId, variableElement);
}
return true;
}
通过roundEnvironment.getElementsAnnotatedWith(BindView.class)得到所有注解elements,Element 用于代表程序的一个元素,这个元素可以是:包、类、接口、变量、方法等多种概念。这里以 Activity 对象作为 Key ,通过 map 来存储不同 Activity 下的所有注解对象。
3.3、生成代码
获取到所有的注解对象后,就可以来构造 bind() 方法了。MethodSpec 是 JavaPoet 提供的一个概念,用于抽象出生成一个函数时需要的基础元素,直接看以下方法应该就可以很容易理解其含义了。
通过 addCode() 方法把需要的参数元素填充进去,循环生成每一行 findViewById 方法:
/**
* 生成方法
*
* @param typeElement 注解对象上层元素对象,即 Activity 对象
* @param variableElementMap Activity 包含的注解对象以及注解的目标对象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法参数名
String parameter = "_" + StringUtils.decapitalize(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的对象类型的全名称
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
完整代码如下:
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = "BindViewProcessor";
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有包含BindView注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因为 BindView 的作用对象是 FIELD,因此 element 可以直接转化为 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封装此 Element 的最里层元素
//如果 Element 直接封装在另一个元素的声明中,则返回该封装元素
//此处表示的即 Activity 类对象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//获取注解值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//将每个包含了 BindView 注解的字段对象以及其注解值保存起来
variableElementMap.put(viewId, variableElement);
}
for (TypeElement key : typeElementMapHashMap.keySet()) {
Map<Integer, VariableElement> elementMap = typeElementMapHashMap.get(key);
PackageElement packageElement = mElementUtils.getPackageOf(key);
String packageName = packageElement.getQualifiedName().toString();
JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 生成 Java 类
*
* @param typeElement 注解对象上层元素对象,即 Activity 对象
* @param variableElementMap Activity 包含的注解对象以及注解的目标对象
* @return
*/
private TypeSpec generateCodeByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
//自动生成的文件以 Activity名 + ViewBinding 进行命名
return TypeSpec.classBuilder(typeElement.getQualifiedName().toString() + "ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodByPoet(typeElement, variableElementMap))
.build();
}
/**
* 生成方法
*
* @param typeElement 注解对象上层元素对象,即 Activity 对象
* @param variableElementMap Activity 包含的注解对象以及注解的目标对象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法参数名
String parameter = "_" + StringUtils.decapitalize(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的对象类型的全名称
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
}