Java 的 Annotation Processor 是非常有用的功能,很多常用的库和框架都使用了 Annotation Processor 来生成代码,比如Butter Knife 就用来生成 findViewById 等代码。
对于一些模板代码使用 Annotation Processor 来自动生成可以提高编写代码的效率和质量,手工编写毕竟容易出现纰漏,工具自动生成是有质量保证的。本文是由 Aitor Viana 编写的如何在 Android Studio 中使用 Annotation Processor 的介绍。
Annotation Processor 主要涉及 3 部分,注解本身(Annotation)、注解处理器(Annotation Processor)以及 在 Android Studio 中如何使用注解处理器。
本文通过如何使用注解来自动生成 Parcelable 接口的代码。
注解
注解就不用详细介绍了,在 Android 编码中应该经常使用。自定义一个表示需要自动生成 Parcelable 的注解: AutoParcel。
在 Android Studio 中创建一个 Java module,名字为 library 。在这个模块中创建这个自定义的 AutoParcel 注解类:
Java
package com.example.autoparcel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // 代表在类级别上才能使用该注解
@Retention(RetentionPolicy.SOURCE) // 代表该注解只存在源代码中,编译后的字节码中不存在
public @interface AutoParcel {}
1
2
3
4
5
6
7
8
9
packagecom.example.autoparcel;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Target(ElementType.TYPE)// 代表在类级别上才能使用该注解
@Retention(RetentionPolicy.SOURCE)// 代表该注解只存在源代码中,编译后的字节码中不存在
public@interfaceAutoParcel{}
由于 AutoParcel 只在源代码中存在,编译后没有在字节码中,所以最最终的运行时是没有影响的。
由于这个 library 库需要在 Android 项目中引用,所以需要修改其 gradle 文件制定编译的 Java 语言版本(library/build.gradle ):
Java
apply plugin: 'java'
// This module will be used in Android projects, need to be
// compatible with Java 1.7
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
...
}
1
2
3
4
5
6
7
8
9
10
11
applyplugin:'java'
// This module will be used in Android projects, need to be
// compatible with Java 1.7
sourceCompatibility=JavaVersion.VERSION_1_7
targetCompatibility=JavaVersion.VERSION_1_7
dependencies{
...
}
注解处理器
注解处理器的功能就是用来读取代码中的注解然后来生成相关的代码。
创建一个 Java module 名字为 “compiler”。 该模块在编译的时候,来获取哪些类使用了 AutoParcel 注解,然后把继承这些类实现 Parcelable 的代码。该模块并不在 Android 项目中引用,只存在于编译的时候。所以这个模块的 Java 版本号可以随意指定(Java 8 、9)。
创建一个 AutoParcelProcessor 类来处理注解:
Java
package com.example.autoparcel.codegen;
@SupportedAnnotationTypes("com.example.autoparcel.AutoParcel")
public final class AutoParcelProcessor extends AbstractProcessor {
@Override
public boolean process(
Set extends TypeElement> annotations,
RoundEnvironment env) {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
packagecom.example.autoparcel.codegen;
@SupportedAnnotationTypes("com.example.autoparcel.AutoParcel")
publicfinalclassAutoParcelProcessorextendsAbstractProcessor{
@Override
publicbooleanprocess(
Set<?extendsTypeElement>annotations,
RoundEnvironmentenv){
...
}
}
对于该类有几点要求:
1. 需要继承至 AbstractProcessor
2. 需要使用类的全称(包含包名)来指定其支持的注解类型(com.example.autoparcel.AutoParcel)
3. 实现 process() 函数,在该函数中来处理所支持的注解类型并生成需要的代码。
下面只是介绍了实现 process() 函数的关键部分,完整代码参考最后的项目。
如果没有其他处理器需要继续处理该注解,则 process() 返回 true。针对我们这个情况,只有 AutoParcelProcessor 需要处理 AutoParcel 注解,所以该函数返回 true。
Java
package com.example.autoparcel.codegen;
...
@Override
public boolean process(
Set extends TypeElement> annotations,
RoundEnvironment env) {
Collection extends Element> annotatedElements =
env.getElementsAnnotatedWith(AutoParcel.class);
List types =
new ImmutableList.Builder()
.addAll(ElementFilter.typesIn(annotatedElements))
.build();
for (TypeElement type : types) {
processType(type);
}
// 返回 true ,其他处理器不关心 AutoParcel 注解
return true;
}
private void processType(TypeElement type) {
String className = generatedSubclassName(type);
String source = generateClass(type, className);
writeSourceFile(className, source, type);
}
private void writeSourceFile(
String className,
String text,
TypeElement originatingType) {
try {
JavaFileObject sourceFile =
processingEnv.getFiler().
createSourceFile(className, originatingType);
Writer writer = sourceFile.openWriter();
try {
writer.write(text);
} finally {
writer.close();
}
} catch (IOException e) {// silent}
}
...
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
packagecom.example.autoparcel.codegen;
...
@Override
publicbooleanprocess(
Set<?extendsTypeElement>annotations,
RoundEnvironmentenv){
Collection<?extendsElement>annotatedElements=
env.getElementsAnnotatedWith(AutoParcel.class);
Listtypes=
newImmutableList.Builder()
.addAll(ElementFilter.typesIn(annotatedElements))
.build();
for(TypeElementtype:types){
processType(type);
}
// 返回 true ,其他处理器不关心 AutoParcel 注解
returntrue;
}
privatevoidprocessType(TypeElementtype){
StringclassName=generatedSubclassName(type);
Stringsource=generateClass(type,className);
writeSourceFile(className,source,type);
}
privatevoidwriteSourceFile(
StringclassName,
Stringtext,
TypeElementoriginatingType){
try{
JavaFileObjectsourceFile=
processingEnv.getFiler().
createSourceFile(className,originatingType);
Writerwriter=sourceFile.openWriter();
try{
writer.write(text);
}finally{
writer.close();
}
}catch(IOExceptione){// silent}
}
...
注解处理器类编写完后,还需要创建一个 java META_INF 文件来告诉系统具有注解处理功能。Java 代码在编译的时候,系统编译器会查找所有的 META_INF 中的注册的注解处理器来处理注解。
在 Android studio 的 compiler 项目中创建如下目录:
compiler/src/main/resources/META_INF/services
在 services 目录下面创建一个名字为 “javax.annotation.processing.Processor” 的文本文件:
该文件中每行一个注解处理器的全名:
Java
com.example.autoparcel.codegen.AutoParcelProcessor
1
2
com.example.autoparcel.codegen.AutoParcelProcessor
这样,注解处理器就创建好了。
在 Android Studio 中使用
在 Android Studio 跟目录的 settings.gradle 中添加前面创建的两个模块:
Java
include ':app', ':compiler', ':library'
1
2
include':app',':compiler',':library'
在 app/build.gradle 中添加前面创建的两个模块为依赖项:
Java
...
dependencies {
...
provided project(':library')
apt project(':compiler')
...
}
1
2
3
4
5
6
7
8
...
dependencies{
...
providedproject(':library')
aptproject(':compiler')
...
}
注意上面 library 项目使用的是 provided 依赖,这是由于 provided 中的代码只在编译的时候存在,并不会打包到最终的应用中去,所以可以使用 provided。 二 compiler 项目为注解编译器,通过使用 android-apt 插件来指定 apt 选项。
apt 是 Annotation Processing Tool 的缩写。
现在就可以在项目中使用 AutoParcel 注解了:
Java
@AutoParcel
public class Foo{
...
}
1
2
3
4
5
@AutoParcel
publicclassFoo{
...
}
本文示例来源于真实的项目 auto-parcel.
使用 Auto-Parcel 就再也不需要手工编写 Parcelable 相关的代码啦。解放双手从今天开始!