annotationProcessor的二三事

  • 内容
  • 评论
  • 相关

简介

当我们使用ROOM、Butterknife等的时候,我们都需要使用到注解,然后相应的三方框架就会在编译期为我们生成对应的代码,为我们生成代码的工具就称为注解处理器(annotation processing)。本篇博客就来介绍如何使用annotation processing为我们生成重复的代码片。

基本步骤

我们在使用Activity以及Fragment的时候,经常需要使用Intent、Bundle来传递数据,不过这样的代码往往都是重复的,本篇博客使用annotation processing来为我们自动完成这些重复代码。

1、创建Android Project。

2、创建annotation Module。这个Module类型为Java Library,仅仅包含需要的注解。

3、创建processor Module。这个Module类型为Java Library,需要依赖我们在第二步中创建的annotation Module,我们在这个Module中进行主要的注解处理以及生成代码,我们可以在这个Module里面使用任何的三方库,因为此Module仅仅在编译期有效,并不会打包到我们的apk中

项目结构如下。

4、添加依赖

app需要添加依赖,annotationProcessor project(':inject-processor')说明inject-processor将被作为注解处理器,当编译app的时候则会调用inject-processor进行处理注解。

annotation Module不需要任何的依赖

processor Module需要依赖annotation Module

Annotation

首先我们在inject-annotation中定义一个注解

@interface:说明是一个注解

@Retention:说明注解的生命期

RetentionPolicy.SOURCE —仅仅在源码级别,当被编译以后,注解被丢弃。

RetentionPolicy.CLASS —class级别,注解不会被加载到jvm中。

RetentionPolicy.RUNTIME — 应用级别,在运行时可以获取到的注解。

@Target:说明注解可以生效的目标

Processor

我们在inject-processor中定义一个AnnotationProcessor。我们自定义的AnnotationProcessor需要继承AbstractProcessor并实现process方法

init(): 初始化,可以通过processingEnvironment参数获取到一些工具类,Filer(生成代码), Messager(打印日志), Elements(获取元素信息),Types可以获取类信息

process():进行注解处理的位置,他的第一个参数为待处理的注解数量,第二个参数可以理解为当前的环境,也就是编译器为我们收集到的所有的注解信息。

getSupportedAnnotationTypes():返回支持的注解类型

getSupportedSourceVersion(): 我们一般返回最新的版本

对于后面两个方法,我们也可以使用注释代替。

@SupportedAnnotationTypes 指定此注解处理器支持的注解,可用 * 指定所有注解

注意:process()一般会回调多次,因为当第一次处理完毕以后,对于第一次生成的代码也需要扫描一次,如果没有对应的注解,则结束,如果还有,则重复前面的操作。所以我们需要根据process的第一个参数来判断。

然后为了让编译器知道我们的注解处理器在哪里,我们需要在主目录下面创建resources文件夹,然后里面创建META-INF文件夹,然后创建services文件夹,再创建javax.annotation.processing.Processor,然后写入AnnotationProcessor的绝对路径。

获取参数

很多三方框架,比如ARouter等会传递参数给注解处理器,下面介绍如何传递参数

定义参数

定义的参数只能是String:String类型

获取参数

前面已经提到了,在init()方法中,我们可以通过ProcessingEnvironment参数获取到一些工具类,同样的,我们也可以获取到参数

工具类的用法

Messager:一般用来打印日志

用法

Filer:用于生成Java文件,一般是用来和JavaPoet一同使用,单独也可以。

Types:获取类型相关

Elements:元素相关

Element

前面已经介绍了,对于注解的处理主要是在process方法中,我们可以通过roundEnvironment.getElementsAnnotatedWith获取到拥有指定注解的元素,我们可以看到,元素一般使用Element表示。所以在真正开始编码之前,我们需要对Element有一个基础的认识。

Element:这个是一个接口,其子类有5个,分别如下。

Tables Are
TypeElement 一个类或接口
VariableElement 一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
PackageElement 一个包程序元素
TypeParameterElement 一般类、接口、方法或构造方法元素的泛型参数

下面分别介绍其方法

可看出其实Element是定义的一个接口,定义了外部调用暴露出的接口

方法 解释
asType 返回此元素的类型,用TypeMirror表示,可以获知Element的数据类型
getKind 返回此元素的种类:包、类、接口、方法、字段。
getModifiers 返回此元素的修饰符
getSimpleName 返回此元素的简单名称,比如activity名
getEnclosingElement 返回封装此元素的外层元素,如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素; 如果此元素是顶层类型,则返回它的包如果此元素是一个包,则返回 null; 如果此元素是一个泛型参数,则返回 null.
getAnnotation 返回此元素针对指定类型的注解(如果存在这样的注解),否则返回 null。注解可以是继承的,也可以是直接存在于此元素上的

五个子类各有各的用处并且有各种独立的方法,在使用的时候可以强制将Element对象转换成其中的任一一种,但是我们需要判断其的类型。

其中最核心的两个子分别是TypeElement和VariableElement

TypeElement

VariableElement

编码

当我们知道了每种Element的常用方法,那么我们就可以使用Filer去生成Java代码,不过推荐使用JavaPoet,相关资料可以看这里。一般来说,编写代码需要两步,第一步,收集所有的注解,第二步,生成代码。生成的代码在app\build\generated\source\apt\debug\下面可以找到。

在process方法中,我们可以通过第一个参数来判断是否需要收集,然后按照业务逻辑存储各个Element,最后使用JavaPoet去生成代码。附上本文博客所说的demo。目前只包含基础数据类型以及String,Serializable,Parcelable。对于其他复合对象,暂时不支持,最新版本可以查看github上面的说明文档。

参考链接:

Android 编译时注解-提升

Annotation Processing : Don’t Repeat Yourself, Generate Your Code.

The 10-Step Guide to Annotation Processing in Android Studio

Github地址:

InjectHelper

评论

0条评论

发表评论

邮箱地址不会被公开。