大家还记得[JakeWharton](https://github.com/JakeWharton)写的翻遍ui注解的库butterknife么?
之前我们都是使用findviewById来查找view,有了butterknife我们摆脱了这种重复代码,通过注解我们解放了生产力,我们可以把更多的经理放到我们的业务和整体的架构上来,而不是不断重复的代码。

在开始前我们需要了解一下Annotation Processor,使用 Annotation Processor 来自动生成可以提高编写代码的效率和质量,手工编写毕竟容易出现纰漏,工具自动生成是有质量保证的。Annotation Processor 主要涉及 3 部分,注解本身(Annotation)、注解处理器(Annotation Processor)以及 如何使用注解器。

一、Hello world

万事先从一个HelloWorld开始,我们可以通过Annotation Processor来自己生成一个类,这个类中的main方法可以打印hello world。

1. 声明注解

需要我们通过Android Studio来新建一个java lib项目 helloAnotation。
其中需要声明一个注解如下:

1
2
3
4
5
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface HelloWorld {
int version() default 0;
}

2. Annotation Processor

再来一个java lib项目我们给它起名字为helloAnotation,这个库中的核心是一个需要继承AbstractProcessor的类,为了快捷实现这个类我们还需要借助javapoet这个工具包。

后面会详细介绍AbstractProcessorjavapoet,我们先来自己定义一个Processor类。

Processor类

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

public class HelloWord extends AbstractProcessor{

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, World! " )
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorldC")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

……
}

声明processor类

如今已经编写完毕了 processor,需要告知系统,我们创建/src/main/resources/META-INF/services文件夹,同时在这个文件夹下创建javax.annotation.processing.Processor文件,在这个文件下,写入我们的processor的完全路径。

1
me.cyning.anotationcore.HelloWord

整个helloAnotation库的build.gradle需要配置如下:

1
2
implementation 'com.squareup:javapoet:1.7.0'
implementation 'com.google.guava:guava:23.3-android

使用

在主项目的入口类TestActivity的类使用这个注解:

1
2
@HelloWorld
public class TestActivity extends AppCompatActivity

build.gradle

1
2
annotationProcessor project(':AnotationCompiler')
implementation project(': helloAnotation')

再次编译就可以在你的主项目的build/generated/source/apt/debug下看到我们生成的类。

理解Annotation Processor

在这个helloWorld的 🌰 中,我们需要掌握两个知识点:AbstractProcessorjavapoet.

AbstractProcessor

AbstractProcessor 函数实现

AbstractProcessor是自定义Annotation Processor核心,它是运行在我们的编译代码阶段,也就是说我们每次在编译前,我们的编译器会根据我们实现的AbstractProcessor类来生成相关类。

AbstractProcessor是个抽象类,有几个函数使我们必须要实现的。

  1. init(ProcessingEnvironment processingEnvironment) 这个可以认为是初始化工作,通过processingEnvironment我们可以得到很多有用的工具类,如Elements,Types和Filer,这个后面我们是会接触到的。
  2. process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
  3. getSupportedAnnotationTypes 是给那些注解使用的,因为可能一个项目中有多个注解器,所有这个是检查步骤,只有在getSupportedAnnotationTypes中就可以继续执行下面的操作。如刚才的helloworld项目中的HelloWorld注解,需要我们声明下:
1
2
3
4
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("me.cyning.anotationan.HelloWorld");
}
  1. getSupportedSourceVersion 指定java版本。通常我们设置为:SourceVersion.latestSupported(),当然你可以自己指定,如指定java 7 SourceVersion.RELEASE_7.

在java 7 之后呢,我们可以通过注解来实现对getSupportedAnnotationTypesgetSupportedSourceVersion 配置。

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({“me.cyning.anotationan.HelloWorld”})
public class HelloWord extends AbstractProcessor{
……
}

变量

由于实现自定义的AbstractProcessor需要借助javax.annotation.processing等jdk的工具包的类,有几个变量和工具类可能是需要了解的。

  1. Element.在获取我们的注解变量时,我们需要根据我们的实际情况来判断,如这个变量是方法,类或者包时,需要借助如下几个类:

Element代表java源文件中的程序构建元素,例如包、类、方法等。

  1. TypeMirror: TypeMirror用来描述类信息,如判断类继承关系等。
    通过Element.asType()接口可以获取Element的TypeMirror。

当TypeMirror是DeclaredType或者TypeVariable时,TypeMirror可以转化成Element:
Element element = processingEviroment.getTypeUtils().asElement(typeMirror);

  1. 工具类:有几个工具类对我们的开发很有用。

    Log
    通过 init方法中获取Messager()对象,messager = processingEnv.getMessager(),在需要打日志的地方打印只需要 messager.printMessage(Diagnostic.Kind.NOTE, logStr)

    通过string获取TypeMirror
    如需要获得android.view.View对应的TypeMirror,只需要在init方法中加入如下代码:

1
2
elementUtils = processingEnv.getElementUtils();
mIViewBindType = elementUtils.getTypeElement(``android.view.View``).asType();

javapoet

javapoet是square公司开源的一个可以生产java文件的库,在Github上的地址:square/javapoet.他是一个很好的工具,有个它,会让你事半功倍。
如在刚才例子中生产的一个类的写法:

1
2
3
4
5
6
7
8
9
10
        MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, World! " )
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorldC")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();

只需要添加修饰符(addModifiers)、传参(addParameter)、添加代码块(addStatement)这样一个方法搞定,至于类的生成似乎更简单。
这样优雅的写法,有没有电到你。

仿ButterKnife

在仿butterknife之前我们可以先预习下我们自定义Annotation Processor 的三部曲:

  1. 自定义注解
  2. 实现Processor类,将这个Processor类放到到resources/META-INF下指定的文件(一个Processor类一行,多个需要写多行)
  3. 写我们的入口类,如butterKnife的ButterKnife.bind(Activity activity)

目的仿ButterKnife学习写自己的注解库, 期望结果,我们写如下类:

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
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

@Inject(R.id.btnTest)
public Button btnTest;

@Inject(R.id.btnTest2)
public Button btnTes2;

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

DroidFinder.bind(this);


btnTes2.setOnClickListener(this);
btnTest.setOnClickListener(this);
}


@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, v.getId() +" ", Toast.LENGTH_LONG).show();
}
}

能通过DroidFinder.bind(this)这个入口和 Inject生成的类,自己去实现哪些findViewById这个重复代码。

自定义注解

由于我们是类中的变量来注解,所以可以直接使用FIELD,而注解 Inject后的value是view的id为整形,所以可以自定义注解如下:

1
2
3
4
5
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Inject {
int value() default 0;
}

这个注解需要放到一个单独的java lib下,我们命名为inject-anotation.

实现Processor类

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
mport com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

import me.cyning.anotationan.Inject;
import me.cyning.anotationcore.inter.ClassTarget;
import me.cyning.anotationcore.inter.FieldTarget;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
@SupportedAnnotationTypes("me.cyning.anotationan.Inject")
public class ViewProcessor extends AbstractProcessor {
private ProcessingEnvironment mProEnv;
Messager messager = null;
public static final String SUFFIX = "_Binder";

public static final String BINDER_INTERFACE = "me.cyning.core.bind.IViewBind";

private TypeMirror mIViewBindType;

private Elements elementUtils;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mProEnv = processingEnv;

messager = processingEnv.getMessager();

elementUtils = processingEnv.getElementUtils();
mIViewBindType = elementUtils.getTypeElement(BINDER_INTERFACE).asType();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

Map<TypeElement, ClassTarget> mTargetMaps = new HashMap<>();
String packageStr = null;
String classNameStr = null;

TypeElement classElem = null;

Set<Element> mElements = (Set<Element>) roundEnv.getElementsAnnotatedWith(Inject.class);
for (Element mElement : mElements) {

//ElementType.FIELD注解可以直接强转VariableElement
VariableElement variableElement = (VariableElement) mElement;
classElem = (TypeElement) variableElement.getEnclosingElement();

String name = variableElement.getSimpleName().toString();
// 获取Inject跟的view的id
int id = variableElement.getAnnotation(Inject.class).value();
TypeMirror type = variableElement.asType();


PackageElement pkgElem = mProEnv.getElementUtils().getPackageOf(classElem);
packageStr = pkgElem.getQualifiedName().toString();
classNameStr = classElem.getSimpleName().toString() + SUFFIX;


ClassTarget mClassTarget = mTargetMaps.get(classElem);
if (mClassTarget == null) {
mClassTarget = new ClassTarget();
mClassTarget.clazzName = classNameStr;
mClassTarget.packageName = packageStr;
mClassTarget.classType = classElem.asType();
mTargetMaps.put(classElem, mClassTarget);
}


FieldTarget fieldTarget = new FieldTarget();
fieldTarget.id = id;
fieldTarget.name = name;
fieldTarget.type = type;
mClassTarget.add(fieldTarget);



log("getEnclosingElement", classElem.getEnclosingElement().toString());
}


log("getEnclosingElement", '1');
log("classNameStr ", classNameStr);
try {


if (classNameStr != null) {


Iterator entries = mTargetMaps.entrySet().iterator();

while (entries.hasNext()) {

Map.Entry<TypeElement, ClassTarget> entry = (Map.Entry<TypeElement, ClassTarget>) entries.next();

TypeElement key = (TypeElement) entry.getKey();

ClassTarget value = (ClassTarget) entry.getValue();


MethodSpec.Builder builder = MethodSpec.methodBuilder("findViews")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(Object.class, "obj", Modifier.FINAL)
.returns(void.class)
.addStatement("$T target = (($T) obj)", value.classType, value.classType);


for (FieldTarget mFieldTarget : value.mFieldTargets) {
builder.addStatement("target." + mFieldTarget.name + " = " + "($T)target.findViewById(" + mFieldTarget.id + ")", mFieldTarget.type);
}

MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.build();


MethodSpec findViews = builder
.build();


TypeSpec bindClazz = TypeSpec.classBuilder(value.clazzName)
.addModifiers(Modifier.PUBLIC)
.addMethod(findViews)
.addMethod(flux)
.addSuperinterface(TypeName.get(mIViewBindType))
.build();

JavaFile javaFile = JavaFile.builder(value.packageName, bindClazz)
.build();

javaFile.writeTo(processingEnv.getFiler());


}


}
} catch (IOException e) {
e.printStackTrace();
}


// try {
//
// } catch (IOException e) {
// e.printStackTrace();
// }
return false;
}


/**
* https://lizhaoxuan.github.io/2016/07/17/apt-Grammar-explanation/
*/
private void log(String tag, Object object) {
if (object != null) {
messager.printMessage(Diagnostic.Kind.NOTE, tag + " --- " + object.toString());
}
}


}