
注解处理器
注解强大的地方在于: 我们可以在运行时或者编译时处理注解. 在编译时或者运行时处理注解的机制都可以称为一个注解处理器.
注解处理器的类型
注解处理器的类型分为两大类:
运行时处理器: 这种机制是在程序运行时利用
反射机制去处理注解.编译时处理器: 这种机制是在程序编译时利用
javac提供的一个apt工具来处理注解.
运行时处理器的特点: 该机制基于反射机制, 因此灵活性很大, 但是却相对耗性能. 编译时处理器的特点:这种方法是在编译时处理注解, 因此不存在性能的问题, 但是灵活性也相对比较低.
在已有的开源库实现中, 普遍是结合编译时处理器和反射机制这两种方法. 举个例子: ButterKnife. 它是一个View注入的库. 内部实现的原理: 编译时利用apt生成findViewById等一些列模板代码, 然后在运行时利用反射去实例生成的模板代码类, 这样完成了一次注入. 从这里可以看出, ButterKnife并没有完全基于编译时注解处理器. 而是加了反射.
这样做的好处: 开发者不需要手动实例apt生成的类. 换句话说: 开发者不需要了解生成类的命令规则. 也许你可能会觉得利用反射会耗性能. 但是仔细想想, 如果没有利用反射的话, 开发者都需要手动编译, 然后再实例化生成的类. 这个过程也是挺繁琐的. 而且, 库内部是有做相应的缓存的, 所以耗的性能还是相对比较低的.
对于反射的利用, 应该适当使用, 而不是避而不用或者滥用.
接下来, 讲讲APT的一些知识, 最后再仿写一个简单的ButterKnife作为实战.
AbstractProcessor
开发注解处理器的第一个步骤就是继承AbstractProcessor. 然后重写其四个方法:
public synchronized void init(ProcessingEnvironment processingEnvironment): 这个方法一般是做一些初始化的工作.public SourceVersion getSupportedSourceVersion(): 该处理器所支持的JDK版本, 一般是支持到最新:SourceVersion.latestSupported().public Set<String> getSupportedAnnotationTypes(): 你所要处理的注解的类型.public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 处理注解的方法. 这个是我们主要实现的方法. 返回值表示当前这个注解类型是否需要被随后的注解处理器处理.true表示不需要,false表示后面的注解处理器可能会对本次处理的注解.
认识这4个方法后, 我们还需要熟悉一下编写处理器涉及到的一些概念和API.
ProcessingEnvironment
ProcessingEnvironment是在init方法传入的. 它表示APT框架的一个处理时上下文. 这个类主要是提供一些工具类. 详细看下面代码注释:
|
|
RoundEnvironment
当APT启动时, 它将会扫描源文件, 然后进入process方法处理, 如果这个过程生成了新的文件的话, 新文件会被APT再次作为输入, 接着process会被apt再次调用, 如此循环下去, 直到没有新的文件产生. RoundEnvironment为处理轮次的上下文环境. 下面是RoundEnvironment的方法简单介绍:
|
|
Element
Element代表Java静态语言结构的元素. 这样听起来可能有点难以理解, 不过看下面的例子就会很好理解了:
|
|
TypeElement: 代表类或接口元素VariableElement: 代表字段, 枚举常量, 方法或构造函数的参数, 局部变量, 资源变量和异常参数.PackageElement: 代表包元素ExecutableElement: 代表的方法, 构造方法,和接口或者类的初始化块.
DeclaredType, TypeElement和TypeMirror
TypeElement表示类或接口元素, 可以从中获取类名, 但是不能获得类本身的信息, 比如父类.
TypeMirror: 表示Java语言中的类型, 其中包括: 基本类型, 声明类型(类类型和接口类型), 数组类型, 类型变量和空类型. 也代表通配类型参数,可执行文件的签名和返回类型.
DeclaredType: 代表声明的类型, 类类型还是接口类型,当然也包括参数化类型,比如Set
开发一个注解处理器
掌握了上面的概念后, 我们开始来开发一个仿ButterKnife的注解处理器.
步骤
继承
AbstractProcessor并重写其中的四个方法.注册注解处理器.
继承AbstractProcessor
|
|
在getSupportedAnnotationTypes()中, 我们直接返回了需要处理的注解的全限定名称集合.
注册处理器
写好的处理器还需要处理告诉Javac, 让它编译时运行我们的处理器. 注册的步骤为: 在项目新建一个resources目录, 然后在resources内新建这样的目录结构: /META-INF/services/. 最后在services中新建文件: javax.annotation.processing.Processor. 并且在文件上填写你的处理器的全路径名称. 例如: com.desperado.processors.ViewBindingProcessor.
其实还有一种更为简单的方法. 添加com.google.auto.service:auto-service:1.0-rc2这个库(Google开源的). 然后在你的处理器上添加注解: @AutoService(Processor.class). 这样, 编译时就会自动帮我们生成注册文件.
如何组织处理器结构
为了不将注解处理器的代码打包进APK. 我们需要将注解和注解处理器分开. 具体的组织如下:

annotations:annotations是一个java lib. 是我们自定义的注解processors:processors也是一个java lib. 是我们的注解处理器viewbinding: 为提供为app的api.
annotations
首先我们在annotation模块定义一个注解
|
|
processors
在讲处理器模块前, 我们先看看期望生成的代码模板:
|
|
生成的代码的类名为: XXXX$ViewBinding
然后在构造函数中对传入的activity中的View进行赋值.
定义处理规则.
BindView只能标注字段.BindView不能标注接口中的字段, 只能标注类中的字段.BindView不能标注抽象类中的字段.BindView不能标注被private,static或者final修饰的字段BindView标注字段所属的类必须是Activity的子类.BindView标注的字段必须是View的子类
定义好这些规则后, 我们就可以来看处理器的代码了.
处理过程
|
|
为了使代码结构更为清晰, 这里将注解标注的字段的信息抽象为ViewField类, 字段所属的类的信息抽象为ViewClass.
|
|
处理过程其实并不复杂, 只是需要我们熟悉API而已, 因此, 接下来总结一下获取一些常用信息的方法:
常用的api
|
|
viewbinding
模板代码已经生成了, 但是我们还需要实例化这些类. 这个工作交由viewbinding. 它提供一些绑定的API给app. 这样我们就不需要手动调用了.
实现的思路: 运行时利用反射去实例化对应的模板类. 为了降低反射的消耗, 内部会做响应的缓存操作.
|
|
使用
注解处理器现在算是开发好了. 最后的步骤就是在app中添加依赖, 然后实现绑定.
在app的build.gradle添加下面的依赖:
|
|
然后在MainActivity中实现注入:
|
|
完…
参考资料