1、概述
APT (Annotation Processing Tool)注解处理工具。在代码编译时,即gradle要执行javac时,
:app:compileDebugJavaWithJavac
会先执行注解处理器,扫描和处理注解,生成一些.java文件,之后才真正执行javac,编译代码生成.class文件。
使用APT知名的第三方库,如:butterknife、dagger2、hilt、databinding等。
关于注解的相关知识可以看: Java注解
2、实现一个简单的注解处理器
2.1 创建注解module
创建Java library Module,命名为 lib-annotation
创建注解 BindView
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
APT起作用在真正执行javac之前,所以注解的作用域可以:RetentionPolicy.SOURCE
2.2 创建注解处理器module
创建Java library Module,命名为 lib-processor
创建注解处理器类,BindViewProcessor,继承AbstractProcessor
public class BindViewProcessor extends AbstractProcessor {
/**
* 生成文件的工具类
*/
private Filer filer;
/**
* 打印类
*/
private Messager messager;
//元素相关工具类
private Elements elementUtils;
private Types typeUtils;
/**
* 初始化
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
}
/**
* 设置支持的版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 设置注解处理器要处理的注解
*/
@Override
public Set getSupportedAnnotationTypes() {
Set types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
/**
* 处理注解,可以生成一些文件
*/
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
/**
* 打印
*/
private void print(String log){
messager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor " + log);
}
}
需要重写的相关方法:
- init():初始化,获取一些有用的系统工具类,比如生成文件、打印信息、处理元素等
- getSupportedSourceVersion():设置支持的版本,一般用:SourceVersion.latestSupported()
- getSupportedAnnotationTypes():设置注解处理器要处理的注解
- process():处理注解,可以生成一些文件。
2.2.1 手动生成 META-INF 信息
注解处理器需要设置 META-INF 信息,才能被编译期自动识别,在
:app:compileDebugJavaWithJavac
就能生效了。
1)在main目录下创建文件夹:resources
2)在resources目录下创建文件夹:META-INF.services
3)在META-INF.services目录下创建文件:javax.annotation.processing.Processor, 文件里写上注解处理器的全路径类名,如:com.yang.apt.lib.processor.BindViewProcessor
在编译时,java编译器(javac)会去META-INF中查找实现了的AbstractProcessor的子类,并且调用该类的process函数,最终生成.java文件。其实就像activity需要注册一样,就是要到META-INF注册 ,javac才知道要给你调用哪个类来处理注解。
META-INF 信息是告诉编译器,我有一个注解处理器,我的注解处理器全路径类名为:com.yang.apt.lib.processor.BindViewProcessor
2.2.2 使用auto-service,自动生成 META-INF 信息(推荐)
上面设置 META-INF 信息是不是很麻烦,这时自动生成 META-INF 信息就来了,只需要一行注解。
在lib-processor的build.gradle,引用auto-service:
apply plugin: 'java-library'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation project(':lib-annotation')
//依赖auto-service,自动生成META-INF 信息
implementation "com.google.auto.service:auto-service:1.0-rc4"
annotationProcessor "com.google.auto.service:auto-service:1.0-rc4"
}
在我们的注解处理器,添加注解:@AutoService
import javax.annotation.processing.Processor;
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
}
构建我们的lib-processor module,会在build目录下,生成META-INF 信息,如图:
auto-service是谷歌的一个库,可以帮助我们自动生成META-INF 信息,它也是个注解处理器,有兴趣的话可以看看它的源码,很简单,可以学习一下
2.3、实现process()方法,生成.java文件
首先,我们在app module的buid.gradle,依赖注解module和注解处理器module:
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(':lib-annotation')
//依赖注解处理器
annotationProcessor project(':lib-processor')
}
在MainActivity中使用注解@BindView:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
我们要实现一个这样的类,达到自动findViewById,类似于:butterknife:
package com.yang.apt.demo;
public class MainActivity_ViewBinding {
public void bind(MainActivity target) {
target.textView = (android.widget.TextView)target.findViewById(2131165337);
}
}
这个类肯定不是手写的,需要我们注解处理器自动生成的,下面代码实现了process()方法,并生成了com.yang.apt.demo.MainActivity_ViewBinding.java文件
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
print("process:开始");
//此方法可能会进入三次,第一次annotations不为空,后面为空
if (annotations.isEmpty()){
return false;
}
//获取被注解的对象集合
Set extends Element> elementsAnnotatedSet = roundEnv.getElementsAnnotatedWith(BindView.class);
Map> map = new HashMap<>();
for (Element element : elementsAnnotatedSet){
// TypeElement//类,注解使用到类上,就是得到类元素
// ExecutableElement//方法,注解使用到方法上,就是得到可执行元素
// VariableElement//变量,注解使用到成员变量上,就是得到变量元素
VariableElement variableElement = (VariableElement) element;
//获取类名
String clazzName = variableElement.getEnclosingElement().getSimpleName().toString();
List variableElements = map.computeIfAbsent(clazzName, k -> new ArrayList<>());
variableElements.add(variableElement);
}
if (!map.isEmpty()){
for (String clazzName : map.keySet()) {
List variableElements = map.get(clazzName);
//生成 **_ViewBinding.java文件
createViewBindingFile(clazzName, variableElements);
}
}
print("process:结束");
//返回false,此方法还会被进入,因为生成了代码,生成了代码中可能还有其他注解,需要再生成代码
return false;
}
/**
* 生成 **_ViewBinding.java文件
*
* 如:com.yang.apt.demo.MainActivity_ViewBinding.java
*/
private void createViewBindingFile(String clazzName, List variableElements) {
TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
//获取包名
String packageName = packageElement.toString();
try {
JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + clazzName + "_ViewBinding");
Writer writer = file.openWriter();
writer.write("package " + packageName + ";");
writer.write("\n\npublic class " + clazzName + "_ViewBinding {");
writer.write("\n\n\tpublic void bind(" + clazzName + " target) {");
for (VariableElement variableElement : variableElements) {
//获取变量名
String variableName = variableElement.getSimpleName().toString();
//获取变量的注解值
int id = variableElement.getAnnotation(BindView.class).value();
//获取变量的类型
TypeMirror typeMirror = variableElement.asType();
writer.write("\n\t\ttarget." + variableName + " = (" + typeMirror.toString() + ")target.findViewById(" + id + ");");
}
writer.write("\n\t}");
writer.write("\n}");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
我们构建一下项目(Make Project),在app module的build目录下,生成了我们需要的Java文件:
可以在MainActivity里直接用,实现了自动findViewById:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MainActivity_ViewBinding().bind(this);
}
}
这样写虽然也行,看着不友好,butterknife是这样用的,每个Activity都是这行代码:
ButterKnife.bind(this);
2.4、反射调用 MainActivity_ViewBinding
新建Android library module,命名为:lib-bindview,新建类:ButterKnife,反射实现调用
new MainActivity_ViewBinding.bind(this);
如下:
public class ButterKnife {
public static void bind(Activity activity){
String clazzName = activity.getClass().getName() + "_ViewBinding";
try {
Class> clazz = Class.forName(clazzName);
Method method = clazz.getMethod("bind", activity.getClass());
method.invoke(clazz.newInstance(), activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改app module的依赖:
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(':lib-annotation')
implementation project(':lib-bindview')
//依赖注解处理器
annotationProcessor project(':lib-processor')
}
修改MainActivity的调用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//new MainActivity_ViewBinding().bind(this);
ButterKnife.bind(this);
}
}
优雅美观了!
3、使用JavaPoet优化process()生成Java文件
在上面process()中,生成.java文件时,直接拼写的字符串,不美观,没有面向对象。如果生成的类代码很多,方法很多,像缩进、换行、括号之类的控制,会很麻烦了。
JavaPoet是大名鼎鼎square公司的一款生成.java文件的开源库。
JavaPoet开源库地址:https://github.com/square/javapoet
官方的文档很详细,如果想看中文的,可以看:https://blog.csdn.net/l540675759/article/details/82931785
使用JavaPoet优化process()后:
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
print("process:开始");
//此方法可能会进入三次,第一次annotations不为空,后面为空
if (annotations.isEmpty()){
return false;
}
//获取被注解的对象集合
Set extends Element> elementsAnnotatedSet = roundEnv.getElementsAnnotatedWith(BindView.class);
Map> map = new HashMap<>();
for (Element element : elementsAnnotatedSet){
// TypeElement//类,注解使用到类上,就是得到类元素
// ExecutableElement//方法,注解使用到方法上,就是得到可执行元素
// VariableElement//变量,注解使用到成员变量上,就是得到变量元素
VariableElement variableElement = (VariableElement) element;
//获取类名
String clazzName = variableElement.getEnclosingElement().getSimpleName().toString();
List variableElements = map.computeIfAbsent(clazzName, k -> new ArrayList<>());
variableElements.add(variableElement);
}
if (!map.isEmpty()){
for (String clazzName : map.keySet()) {
List variableElements = map.get(clazzName);
//生成 **_ViewBinding.java文件
createViewBindingFileByJavaPoet(clazzName, variableElements);
}
}
print("process:结束");
//返回false,此方法还会被进入,因为生成了代码,生成了代码中可能还有其他注解,需要再生成代码
return false;
}
/**
* 使用JavaPoet生成 **_ViewBinding.java文件
*
* 如:com.yang.apt.demo.MainActivity_ViewBinding.java
*/
private void createViewBindingFileByJavaPoet(String clazzName, List variableElements) {
//获取类元素
TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
//获取包名
String packageName = packageElement.toString();
try {
//构建方法:public void bind(MainActivity target)
MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(typeElement.asType()), "target")
.returns(void.class);
for (VariableElement variableElement : variableElements) {
//获取变量名
String variableName = variableElement.getSimpleName().toString();
//获取变量的注解值
int id = variableElement.getAnnotation(BindView.class).value();
//获取变量的类型
TypeMirror typeMirror = variableElement.asType();
//给方法添加语句:target.textView = (android.widget.TextView)target.findViewById(2131165338);
bindBuilder.addStatement("target.$L = ($L)target.findViewById($L)", variableName, typeMirror.toString(), id);
}
//构建类: MainActivity_ViewBinding
TypeSpec typeSpec = TypeSpec.classBuilder(clazzName + "_ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(bindBuilder.build())
.build();
//构建Java文件
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
//写入.java文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
自动生成的代码,和我们手动拼接的差不多,这样写更显得高大上,优雅!
源码
本文示例代码 源码:https://github.com/jinxiyang/AptDemo
项目结构:
- app : Android Module,可运行的APP,依赖lib-annotation、lib-processor、lib-bindview
- lib-annotation : Java Library module,注解库
- lib-processor:Java Library module,注解处理器库,依赖lib-annotation、auto-service、JavaPoet (推荐)
- lib-processor2:Java Library module,注解处理器库,另一种写法,依赖lib-annotation,手写META-INF信息
- lib-bindview:Android Library module,反射执行viewbinding,依赖lib-annotation
如图: