Annotation Processing Tool ,即注解处理器。一般用来处理自定义的注解,然后根据注解生成一个辅助类。最著名的例子就是@BindView注解。
注意,这是在编译时扫描所以继承AbstractProcessor类,然后调用process方法去处理。因为是在编译的时候处理的,所以很多时候需要用到反射。
总体流程基本上分为这三个部分。但是有些时候,我们会在第2部到第3步之间创建一个android library库,然后在android app中直接引用这个android library
本例只看一下流程,对注解的处理比较粗糙。
创建java library库,命名为lib-anno,该库用来定义注解
在该库中创建一个注解
// 当前注解所在包为 com.hhh.lib_anno
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int id();
}
再创建一个java library库,命名为lib-processor,该库用来处理注解
首先在该module的build.gradle文件中引入依赖
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 引入注解的库
implementation project(':lib-anno')
// AutoService 注解
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation 'com.google.auto.service:auto-service-annotations:1.0-rc6'
// 用于生成Java文件
implementation 'com.squareup:javapoet:1.12.1'
}
sourceCompatibility = "8"
targetCompatibility = "8"
自定义Processor处理自定义的注解
// 所在包 com.hhh.lib_processor
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({
"com.hhh.lib_anno.BindView"
})
public class MyProcessor extends AbstractProcessor {
/**
* @param set 需要处理的注解。也就是SupportedAnnotationTypes注解里面的内容
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"开始处理注解了啦啦啦啦啦 ");
// 获取所有被BindView注解的元素
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
if (set == null) {
// 直接报错
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"没找到任何元素");
}
for (Element e : bindViewElements) {
// 判断被BindView注解的元素是否是全局变量
if (e.getKind() == ElementKind.FIELD) {
VariableElement variableElement = (VariableElement) e;
// 输出被注解的元素
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"variableElement : "+variableElement);
BindView annotation = variableElement.getAnnotation(BindView.class);
int id = annotation.id();
// 输出注解的id
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"id : "+id);
}
}
return true;
}
}
当前process方法中仅仅是输出被BindView注解的元素以及对应的id,没有做其他处理
@AutoService
google开源库,用来进行组件化开发的。如果没有该注解,可以自己手动注册processor
@SupportedSourceVersion
提供支持的java版本号。如果没有该注解,需要重写getSupportedSourceVersion方法
SupportedAnnotationTypes
提供需要处理的注解。如果没有该注解,需要重写getSupportedAnnotationTypes方法
在android app中使用
在模块的build.gradle引入注解库,并配置注解处理库
// 在dependcies方法里面引入
// 引入注解库
implementation project(':lib-anno')
// apt 配置注解处理库
annotationProcessor project(':lib-processor')
在Activity中使用注解
public class MainActivity extends AppCompatActivity {
//R.id.tv是在xml文件中的TextView的id
@BindView(id = R.id.tv)
public TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
好吧,三个步骤完成了,最后build一下,输出如下所示:
警告: 开始处理注解了啦啦啦啦啦
警告: variableElement : mTextView
警告: id : 2131165359
可以看到,输出被注解的元素mTextView 以及id。
但是这个注解有什么用?好吧,没用,就是看个流程,接下来重新
接下来,将在AbstractProcessor处理BindView注解,将被BindView注解的View自动初始化,即做以下处理
mTextView = findViewById(R.id.tv);
但是要实现上面这一句咋办?生成一个辅助类呗,然后将对应的Activity传入进去,最终生成的类要类似这样的:
// 所在包 com.hhh.aptdemo
public class MainActivity$Helper implements IInjection {
@Override
public void inject(Object obj) {
MainActivity a = (MainActivity) obj;
a.mTextView = a.findViewById(2131165359);
}
}
其中,IInjection 是自己定义的一个接口,方便对外提供。MainActivity名称是动态变化的,如果在其他Activity中,就需要换成其他Activity的名字。
在lib-anno中创建IInjection接口。要求所有被BindView注解元素所在类生成的辅助类都要继承它
package com.hhh.lib_anno;
public interface IInjection {
void inject(Object activity);
}
重新写个Procesoor在app下使用

public class MainActivity extends AppCompatActivity {
//R.id.tv是在xml文件中的TextView的id
@BindView(id = R.id.tv)
public TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainActivity$Helper helper = new MainActivity$Helper();
helper.inject(this);
mTextView.setText(" hello world ,this is test");
}
}
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({
"com.hhh.lib_anno.BindView"
})
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 获取所有被BindView注解的元素
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
if (set == null) {
// 直接报错
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"没找到任何元素");
}
// 用来存储Activity以及Activity中被BindView注解的元素
Map<TypeElement, Set<ViewInfo>> map = new HashMap<>();
for (Element e : bindViewElements) {
// 判断被BindView注解的元素是否是全局变量
if (e.getKind() == ElementKind.FIELD) {
// 被注解的元素
VariableElement variableElement = (VariableElement) e;
// 获取有BindView注解的元素的最直接外层
// 例如在一个类A的全局变量B使用了注解,那么最直接外层元素就是类A
Element enclosingElement = variableElement.getEnclosingElement();
// 判断当前封装了BindView注解的最里层元素是否是类
if (enclosingElement.getKind() == ElementKind.CLASS) {
TypeElement classEle = (TypeElement) enclosingElement;
// 填充集合,将BindView注解的View的名称与id一一对应起来,保存为ViewInfo
Set<ViewInfo> viewInfos = map.get(classEle);
if (viewInfos == null) {
viewInfos = new HashSet<>();
map.put(classEle, viewInfos);
}
BindView annotation = variableElement.getAnnotation(BindView.class);
ViewInfo info = new ViewInfo(variableElement.getSimpleName().toString(), annotation.id());
viewInfos.add(info);
}
}
}
createFile(map);
return true;
}
private void createFile(Map<TypeElement, Set<ViewInfo>> map) {
for (TypeElement typeElement : map.keySet()) {
// 获取到类。即含有BindView注解元素的类。本例子中就是MainActivity
Name simpleName = typeElement.getSimpleName();
// 创建代码块
CodeBlock.Builder builder = CodeBlock.builder()
// 强制转换为对应的类
.addStatement("$N a = ($N) obj", typeElement.getSimpleName(), typeElement.getSimpleName());
for (ViewInfo info : map.get(typeElement)) {
builder.addStatement("a.$L = a.findViewById($L)", info.viewName, info.id);
}
// 创建参数
ParameterSpec objPara = ParameterSpec.builder(Object.class, "obj")
.build();
// 创建方法
MethodSpec injectMethod = MethodSpec.methodBuilder("inject")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(objPara)
.addCode(builder.build())
.build();
// 创建类
TypeSpec helperClass = TypeSpec.classBuilder(simpleName.toString() + "$Helper")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(IInjection.class)
.addMethod(injectMethod)
.build();
System.out.println(typeElement.getQualifiedName());
// 获取当前类所在的包
PackageElement packageEle = processingEnv.getElementUtils().getPackageOf(typeElement);
// 创建java文件
JavaFile javaFile = JavaFile.builder(packageEle.getQualifiedName().toString(), helperClass).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
创建一个Android Library的module
该module的作用就是为了想外提供api,隐藏生成的辅助类,不要app直接访问辅助类。
首先,build.gradle文件中,需要引用 IInjection 所在的库
implementation project(':lib-anno')
再创建一个类,用来提供api
public class Registrar {
private Registrar() {
}
private static class SingleHolder {
public static final Registrar INSTANCE = new Registrar();
}
public static Registrar getInstance() {
return SingleHolder.INSTANCE;
}
public void register(Activity activity) {
// 通过反射获取Activity的辅助类,然后创建实例,在
try {
Class<?> clazz = Class.forName(activity.getClass().getName() + "$Helper");
// 因为规定所有的Helper类都继承了IInjection,所以直接强制转换
IInjection iInjection = (IInjection) clazz.newInstance();
iInjection.inject(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里直接通过反射来获取生成的Helper类,在使用的时候根本不需要知道生成的Helper类的名称以及所在地址
在app中使用
首先在build.gradle中引入common库
// 引入common库
implementation project(':lib-common')
然后再Activity中使用注解,并注册
public class MainActivity extends AppCompatActivity {
//R.id.tv是在xml文件中的TextView的id
@BindView(id = R.id.tv)
public TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Registrar.getInstance().register(this);
mTextView.setText(" hello world ,this is test");
}
}
ok,完成了,可以看到mTextView并没有使用findViewById方法,直接可以使用注解来找到对应的id
在使用apt的时候,有一些相关只是是需要了解的。
元素,可以是方法,可以是类,也可是是属性。可以认为只要能被注解的东西都是元素
ExecutableElement:表示方法,构造方法,初始化器
PackageElement:表示包
TypeElement:表示类,接口
TypeParameterElement:表示类,接口,方法的泛型类型。
VariableElement:表示一个字段,enum常量,方法的参数,局部变量以及异常参数
子类中有些特殊的方法不在介绍,直接看文档
该类中只有一个常用的方法,即getKind,返回类型为TypeKind
TypeKind的作用是判断元素的类型,与Element的getKind方法返回的类型是完全不同的,但是两者在作用上类似。
创建代码块的,当然也可以自己用StringBuild一个一个字符的敲。
该类支持占位符,使用$符号作为占位符的前缀
其实常用到的也就是 L , L, L,N, S , S, S,T