背景:前些天看过的butterKnife解析,感觉自己对注解这一块的了解缺口很大,所以稍微学习了一下,感觉还是很好玩的,所以记录下来。本文长期更新维护。
注解是什么?
这个东西其实一直活在我们的代码中,比如继承的@Override,到butterKnife中的@BindView,但是我们(我)可能习惯性的忽略它。相对于长长的重复性代码(findViewById(xxx)),它更加简介,可读性强,后期维护也比较方便。至于缺点,我想到的是自定义注解在没有说明完好的情况下可能对后来者不是很友好,存在一定的学习成本;另外一点是背后的实现逻辑交给注解器来处理,一旦注解类型多了,处理的逻辑也就多了,因此学习、维护与改错都比较难,相对于直接嵌入工程使用的小段代码而言。
注解类型?
注解中存在元注解(概念上类似与基本数据结构int,short,long等),共有四种@Retention, @Target, @Inherited, @Documented。
一个注解大概长这样子:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
比较重要的是@Retention, @Target
@Retention 是指注解保留的范围,默认有三种:
- SOURCE 源码级别注解,该类型的注解信息只会保留在.java源码中,>.class中不会存在
- CLASS 编译时注解,会保留在.java和.class中,执行时会被java虚拟机丢弃
- RUNTIME 运行时注解,不仅是.java .class,还会加载到虚拟机中,可以通过反射机制读取注解信息(方便)
@Target 取值是一个ElementType的类型数组(后面会讲到),用来指定我们注解的使用范围,有这么几种:
其中最后两种是java 8新增的,在之前的版本中只允许在声明式前使用注解,但是现在可以用在type之前:
TYPE_PARAMETER 用来表示类型参数,比如
class test{
//...
}
TYPE_USE 适用范围更广,适用于标注的各式形态,比如:
Module m =(@GzoomAnnotation Module)new Object();
更加详细的可以参考这篇文章
注解的过程?
注解大体上又分为运行时注解和编译时注解,简单的说就是以什么时候处理注解为分界线。
- 运行时注解相对比较简单,可以看成“标签”,给属性(或者方法等等)特殊化,在需要的时候找到这些标签的标记,这其中使用了反射的方法。这种方法的优点是方便,简单易学,本质上就是在运行时进行代码调用,和我们平常的反射区别不是很大;缺点还是反射,使得性能比较低。推荐文章:Android中的自定义注解(反射实现-运行时注解)(++还有一点,在Android平台上,查询注解的效率比较低,特别是在Android 4.0之前的系统上,可以看看这个Bug,其中也推荐我们用编译时注解,所以个人观点是慎用++)
- 编译时注解不需要适用反射,在编译阶段它不能操作已经有的java文件,因此为了实现我们的“目的”,我们可以创造目标java文件来实现代码逻辑功能。
编译时注解流程