Java注解处理器

概述

注解处理器(Annotation Processor),是javac的一个工具,用来在编译时扫描和处理注解。

一个注解处理器以Java代码(或者编译过得字节码)作为输入,生成.java文件作为输出。

接下来我们模仿ButterKnife 实现一个@BindView的注解来了解Java注解处理器的使用。

创建项目

apt-project

模块说明:

bindview-annotation:定义注解,@BindView
bindview-compiler:定义注解处理器,处理被@BindView标记的代码,并在编译时生成xxxActivity_ViewBinding.java
bindview-api:工具类,调用xxxActivity_ViewBinding.java中的方法,实现View的绑定。

bindview-annotation(自定义注解)

创建注解类BindView

1
2
3
4
5
@Retention(RetentionPolicy.CLASS)  // 表示编译时注解
@Target(ElementType.FIELD) // 表示注解范围为类成员
public @interface BindView {
int value(); // 用于获取对应View的id
}

bindview-compiler(注解处理器)

在该module的build.gradle中添加如下代码:

1
2
3
4
dependencies {
implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation project(':bindview-annotation')
}

创建BindViewProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 该注解用来自动生成META-INF/services/javax.annotation.processing.Processor文件,
// 并在该文件注册BindViewProcessor这个注解处理器
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

// 用来在处理注解的过程中打印日志
private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);

mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}

/**
* 声明该注解所处理的注解类型,也可以直接在注解处理器的类声明添加如下注解:
* @SupportedAnnotationTypes({"com.github.xch168.bindview.annotation.BindView"})
* @return 接受处理的所有注解类型的集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}

/**
* 指定使用的Java版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();

// 得到所有的注解
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}

// 通过遍历mProxy,创建java文件
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
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();
mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + " error!");
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
}
}

ClassCreatorProxy是创建Java代码的代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ClassCreatorProxy {

private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
mPackageName = packageName;
mBindingClassName = className + "_ViewBinding";
}

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\n");
builder.append("\n");
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");

generateMethods(builder);

builder.append("\n");
builder.append("}\n");
return builder.toString();
}

private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host) {\n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
builder.append("host." + name).append(" = ");
builder.append("(" + type + ")(((android.app.Activity)host).findViewById(" + id + "));\n");
}
builder.append(" }\n");
}

public String getProxyClassFullName() {
return mPackageName + "." + mBindingClassName;
}

public TypeElement getTypeElement() {
return mTypeElement;
}
}

bindview-api(注解生成代码的调用工具类)

创建注解工具类BindViewTool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BindViewTool {

/**
* 通过翻车找到对应的ViewBinding类,然后调用其中的bind方法,完成View的绑定
* @param activity
*/
public static void bind(Activity activity) {
Class clz = activity.getClass();
try {
Class bindViewClass = Class.forName(clz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", 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();
}
}
}

使用注解

在app模块的build.gradle中添加如下代码:

1
2
3
4
5
6
7
dependencies {
implementation project(':bindview-annotation')
implementation project(':bindview-api')

// Gradle 2.2及后面的版本都是用annotationProcessor,早先的版本是用apt,现以废弃,故不再介绍
annotationProcessor project(':bindview-compiler')
}

在MainActivity中使用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {

@BindView(R.id.content)
TextView mContentText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

BindViewTool.bind(this);

mContentText.setText("From BindView");
}
}

运行后生成的代码(路径 app/build/generated/source/apt):

apt-generated-code

MainActivity_ViewBinding代码:

1
2
3
4
5
public class MainActivity_ViewBinding {
public void bind(com.github.xch168.annotationdemo.MainActivity host) {
host.mContentText = (android.widget.TextView)(((android.app.Activity)host).findViewById(2131165228));
}
}

通过javapoet生成代码

上面生成代码的部分,是通过字符串拼接,过程非常繁琐。接下来就介绍一种更优雅的方式,使用javapoet

添加依赖:

1
2
3
dependencies {
implementation 'com.squareup:javapoet:1.10.0'
}

ClassCreatorProxy中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ClassCreatorProxy {
// ……

public TypeSpec generateJavaCodeByJavapoet() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodsByJavapoet())
.build();
return bindingClass;
}

private MethodSpec generateMethodsByJavapoet() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");

for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById(" + id + "));");
}
return methodBuilder.build();
}

public String getPackageName() {
return mPackageName;
}
}

BindViewProcessor中调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// ...

for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCodeByJavapoet()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
}

相比用StringBuilder拼Java代码,明显简介很多,且生成的代码是一样的。

参考文章

  1. 【Android】APT
  2. 自定义Java注解处理器
  3. 一小时搞明白注解处理器(Annotation Processor Tool)
  4. Android APT及基于APT的简单应用
  5. Android 编译时注解-提升-butterknife
  6. javapoet
文章目录
  1. 1. 概述
  2. 2. 创建项目
  3. 3. bindview-annotation(自定义注解)
  4. 4. bindview-compiler(注解处理器)
  5. 5. bindview-api(注解生成代码的调用工具类)
  6. 6. 使用注解
  7. 7. 通过javapoet生成代码
  8. 8. 参考文章
,