Android中使用AbstractProcessor探索

本篇博客主要讲解 Annotation Processor 注释处理器
主要围绕以下几点进行讲解:

  • 什么是注释处理
  • 它可以做什么
  • 示例

什么是注释处理

注释,一种元数据形式提供了一个不属于程序本身的程序的数据。注释对它们注释的代码的操作没有直接的影响。

  • 按照处理时期,注解分为两种类型
    • 运行时注解
    • 编译时注解 doc

正确认识 运行时 、 编译时

  • 我们并不是在运行时使用反射来评估注释(运行时间=应用程序运行的时间)。

  • 注释处理在编译时进行(编译时= java编译器编译java源代码的时间)。

它可以做什么

  • 编译器的信息 - 编译器可以使用注释来检测错误或抑制警告。
  • 编译时和部署时处理 - 软件工具可以处理注释信息以生成代码,XML文件等。
  • 运行时处理 - 有些批注可在运行时检查。

示例

运行时Annotation解析

运行时Annotation是指@Retention为RUNTIME的Annotation

  • 解析Annotation的API:

    1
    2
    3
    4
    T getAnnotation(Class annotationClass) //返回程序上存在和指定类型的注解
    Annotation[] getAnnotations() //返回程序元素上存在的所有注解
    boolean isAnnotationPresent(Annotation) //判断程序元素上是否包含指定类型的注解
    Annotation[] getDeclaredAnnotations() //返回直接存在在元素上的所有注解,不包含继承的注解
  • 获取注解的信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private void processAnnotation(Class<?> clazz) {
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
    if (field.isAnnotationPresent(Record.class)) {
    Record myAnnotation = field.getAnnotation(Record.class);
    Log.d("DEBUG", "### id:value: " + myAnnotation.value());
    }
    }
    }

编译时Annotation解析

以下是实现一个简单的使用AbstractProcessor示例

主要功能:

  • 用于注释在类上,统计所有被添加注释的class 并获取他们的包名类名,以及自定义的一个author信息。
  • 在top.goluck.annotations_2017_4_10包名下生成类名为ClasssUtil的文件,为每个注解类生成一个如下方法
    1
    2
    3
     public static String toMainActivity() {//此MainActivity就是注解类类名
    return "author:注解类的author的值\nclass:注解类的类名\npackageName:注解类的包名类";
    }
创建一个annotation模块的Java Library

新建自定义的annotation Record

1
2
3
4
5
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Record {
String author() default "luck";//luck是给的默认值
}

注:以下是关于元注解的介绍

元注解是指注解的注解。包括 @Retention @Target @Document @Inherited 四种。

  • @Retention: 定义注解的保留策略

    @Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含
    @Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
    @Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 
  • @Target:定义注解的作用目标

    @Target(ElementType.TYPE)   //接口、类、枚举、注解
    @Target(ElementType.FIELD) //字段、枚举的常量
    @Target(ElementType.METHOD) //方法
    @Target(ElementType.PARAMETER) //方法参数
    @Target(ElementType.CONSTRUCTOR)  //构造函数
    @Target(ElementType.LOCAL_VARIABLE)//局部变量
    @Target(ElementType.ANNOTATION_TYPE)//注解
    @Target(ElementType.PACKAGE) ///包 
    
    @Target({ElementType.TYPE,ElementType.METHOD})        
    以上示例:这个注解可以是类注解,也可以是方法的注解
  • @Document:说明该注解将被包含在javadoc中

  • @Inherited:说明子类可以继承父类中的该注解

创建一个compiler模块的Java Library
build.gradle 添加依赖
1
2
3
4
5
6
7
8
9
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project (':annotation')
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
}
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
  • compile project (‘:annotation’): 引用注解的模块
  • javapoet: 自动生成类的辅助库 doc
  • auto-service: 自动生成META-INF/services/javax.annotation.processing.Processor文件的辅助库doc
实现AbstractProcessor类
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
@AutoService(Processor.class)
public class RecordProcessor extends AbstractProcessor {

private Messager messager;//可用于在开发工具Messages栏打印信息

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(Record.class.getCanonicalName());
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//实现需要处理注释的方法
// ...
//扫描,处理注解,生成 java 文件等
// ...

//---------------------------------------------------------------------------------------------------------
//以下是简单的一个处理示例
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Record.class);
TypeSpec.Builder builder = TypeSpec.classBuilder("ClasssUtil").addModifiers(Modifier.PUBLIC);
if(elements!=null && elements.size()>0) {
for (Element element : elements) {
if (element.getModifiers().contains(Modifier.PUBLIC) && !(element.getModifiers().contains(Modifier.ABSTRACT))) {//不支持非public的class
Record router = element.getAnnotation(Record.class);
StringBuilder sb = new StringBuilder();
sb.append("return \"author:" + router.author()+"\\n\"+\n \"class:"+ element.getSimpleName().toString() +"\\n\"+\n \"packageName:"+ (element==null?null:element.asType())+ "\"");
MethodSpec methodSpec = MethodSpec.methodBuilder("to" + element.getSimpleName().toString())
.addJavadoc("to" + element.getSimpleName().toString() + "\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addStatement(sb.toString())
.returns(String.class)
.build();
builder.addMethod(methodSpec);
}
}
try {
TypeSpec generateClass = builder.build();
JavaFile javaFile = JavaFile.builder("top.goluck.annotations_2017_4_10", generateClass).build();
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Couldn't generate class");
}
}
//---------------------------------------------------------------------------------------------------------

return true;
}
}

注:以下是关于实现AbstractProcessor类的方法介绍

  • init(ProcessingEnvironment processingEnv)

    所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以ProcessingEnvironment作为参数。
    ProcessingEnvironment 提供了一些实用的工具类Elements, Types和Filer。
  • process(Set<? extends TypeElement> annoations, RoundEnvironment env)

    主要的逻辑处理都在这里,这个方法里面可以实现扫描,处理注解,生成 java 文件。使用RoundEnvironment 参数,可以查询被特定注解标注的元素。
  • getSupportedAnnotationTypes()

    在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String集合,包含了你的注解处理器想要处理的注解类型的全称。
主app模块中使用
在app模块中引用处理器,需要通过apt plugin,所以需要在根目录build.gradle声明:
1
2
3
4
5
6
7
8
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
在app模块的build.grale中引入依赖:
1
2
3
4
5
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
compile project (':annotation')
}
使用注解声明类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Record(author = "new luck")
public class MainActivity extends AppCompatActivity {

private boolean arg = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text = (TextView) findViewById(R.id.text);
ClasssUtil classsUtil = new ClasssUtil();
text.setText(classsUtil.toMainActivity());
}
}

注意:ClasssUtil 在编译之前是不存在的,编译之后就可以直接使用。

编译代码,生成代码:
1
2
3
4
5
6
7
8
9
10
package top.goluck.annotations_2017_4_10;

public class ClasssUtil {
public ClasssUtil() {
}

public static String toMainActivity() {
return "author:new luck\nclass:MainActivity\npackageName:top.goluck.annotations_2017_4_10.MainActivity";
}
}

相关参考博文