什么是APT
APT全称 Annotation Process Tool
,是Java提供的注解处理工具,能够帮助开发者在编译阶段生成所需要的可执行代码。
在Android开发中,著名的ButterKnife
、Dagger2
、ARouter
都是使用了APT技术,所以作为一名Android开发者,还是有必要了解一下APT技术的使用。
Android中使用APT
在Android工程中使用APT,首先需要创建两个Java Module,一个是annotation
(注解module,也可以是Android Module),一个是compiler
(这个是编译处理器,必须是java module)。
这里以Demo:https://gitee.com/renzhongrui/android-learn/tree/master/031-android-butterknife为例,使用APT编写简单的butterknife原理。
一、创建lib_butterknife_annotation注解module,并添加注解BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
@Target:用于描述注解的使用范围
常用注解ElementType如下:
ElementType | 说明 |
FIELD | 作用于成员变量上 |
METHOD | 作用于方法上 |
TYPE | 用于描述类Class、接口interface(包括注解annotation类型) 或enum |
TYPE_USE | 作用于任何类型上 |
@Retention
定义注解保留周期,一共有三种策略,定义在RetentionPolicy枚举中:
RetentionPolicy | 说明 |
source | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃,被编译器忽略。 |
class | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。 |
runtime | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。 |
二、创建lib_butterknife_compiler注解处理器
1、创建java module
注解处理器,并在build.gradle中添加依赖:
// 添加自定义注解依赖
implementation project(':lib_butterknife_annotation')
// 添加注解处理库
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
// 创建的java module默认会使用android.jar中的api,这里需要引入本地jdk中的jar包
implementation files ('D:/Program Files/Java/jdk1.8.0_74/jre/lib/rt.jar')
这里需要注意一点,如果代码中引入不了javax包下的jdk中的类,表示依赖的是android.jar下的jdk,所以需要手动引入一下本地的jdk的jar包,在项目工程的Exterenal Libraries
下可以找到当前工程的jdk目录。
2、创建AnnotationCompiler
类,需要继承AbstractProcessor
类
AutoService
会自动在build/classes
输入目录下生成文件META-INF/services/javax.annotation.processing.Processor
,在javax.annotation.processing.Processor
的情况下,如果一个jar中包含metadata文件,并且在javac的classpath下,javac会自动加载这个jar,同时包含它的普通注解编译环境。
3、继承AbstractProcessor
类后需要重写四个方法
init
初始化方法,会被首先调用,在init方法中初始化文件操作对象Filer实例和打印对象Messager实例。
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager(); // 打印对象
filer = processingEnv.getFiler(); // 文件操作对象
}
getSupportedSourceVersion
声明支持的Java版。
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
getSupportedAnnotationTypes
获取注解类型,这个类型就是获取之前声明的注解类型。
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
process
process方法主要是执行生成java文件,然后在java文件中写入想在编译阶段执行的代码逻辑。
这里TypeElement类型有以下几种:
- TypeElement :类或者接口类型
- VariableElement:成员变量
- ExecuteableElement:成员方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 得到BindView注解的Element节点
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
// 存储每个Activity中的所有注解属性
HashMap<String, List<VariableElement>> map = new HashMap<>();
for (Element element : bindViewElements) {
// 转成属性节点
VariableElement e = (VariableElement) element;
// 获取上一个节点,也就是类名
TypeElement typeElement = (TypeElement) e.getEnclosingElement();
// 获取类名
String activityName = typeElement.getSimpleName().toString();
//缓存对应类的所有节点
List<VariableElement> variableElementList = map.get(activityName);
if (variableElementList == null) {
variableElementList = new ArrayList<>();
map.put(activityName, variableElementList);
}
// 缓存所有的属性注解节点
variableElementList.add(e);
}
// 开始生成java文件,将逻辑自动写入到java文件中
if (map.size() > 0) {
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
// 得到类上的所有节点
List<VariableElement> variableElements = map.get(activityName);
//得到包名
String packageName = getPackageName(variableElements.get(0));
messager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
// 自定义类名,仿造butterknife
String newClass = activityName + "$$ViewBind";
try {
// 创建java文件
JavaFileObject javaFile = filer.createSourceFile(packageName + "." + newClass);
// 写入代码
writer = javaFile.openWriter();
StringBuffer buffer = new StringBuffer();
// 拼接代码
buffer.append("package " + packageName + ";\n");
buffer.append("import android.view.View;\n");
buffer.append("public class " + newClass + "{\n");
buffer.append("public " + newClass + "(final " + packageName + "." + activityName + " target){\n");
for (VariableElement element : variableElements) {
String fieldName = element.getSimpleName().toString();
int resId = element.getAnnotation(BindView.class).value();
TypeMirror typeMirror = element.asType();
// 执行findViewById
buffer.append("target." + fieldName + "=target.findViewById(" + resId + ");\n");
}
buffer.append("}\n}");
writer.write(buffer.toString());
} catch (IOException e) {
e.printStackTrace();
}
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
测试:
在app/build.gradle中添加依赖:
implementation project(':lib_butterknife_annotation')
annotationProcessor project(':lib_butterknife_compiler')
在MainActivity中添加代码:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn_write)
Button btn_write;
@BindView(R.id.btn_read)
Button btn_read;
}
build一下在路径031-android-butterknife\app\build\generated\source\apt\debug\com\learn\eventbus\MainActivity$$ViewBind.java下
最终生成的类如下:
package com.learn.eventbus;
import android.view.View;
public class MainActivity$$ViewBind{
public MainActivity$$ViewBind(final com.learn.eventbus.MainActivity target){
target.btn_write=target.findViewById(2131165250);
target.btn_read=target.findViewById(2131165249);
}
}
4、当然最后要调用一下
创建ButterKnife.java
类,添加代码:
public class ButterKnife {
public static void bind(Object activity) {
// 获取当前类
Class<?> aClass = activity.getClass();
// 自定义生成的类名
String bindName = aClass.getName() + "$$ViewBind";
try {
// 执行带参数的构造方法,这里调用的就是上面生成的类的构造,会执行findViewById方法
Class<?> bindClazz = Class.forName(bindName);
Constructor<?> constructor = bindClazz.getConstructor(activity.getClass());
constructor.newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上就是使用APT实现ButterKnife
核心原理,感兴趣的可以下载Demo测试一下。
最后如果本文对您有帮助,麻烦点个赞,如果想手机浏览。