对于Android注解,或多或少都有一点接触,但相信大多数人都是在使用其它依赖库的时候接触的。由于有些库若是你想使用它就必须使用它所提供的注解。例如:ButterKnife、Dagger二、Room等等。java
至于为什么使用注解?使用过的应该都知道,最明显的就是方便、简洁。经过使用注解能够在项目编译阶段,帮助咱们自动生成一些重复的代码,减轻咱们的负担。典型的ButterKnife本质就是使用Android注解,经过注解来减小咱们对view.findViewById的编写,提升咱们的开发效率。上一个系列(AAC)的Room也是同样,咱们能够简单的回顾一下:android
@Entity(tableName = "contacts") data class ContactsModel( @PrimaryKey @ColumnInfo(name = "contacts_id") val id: Int, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "phone") val phone: String )
经过使用注解来定义一个实体表,也就10行左右的代码。若是要咱们所有本身写那绝对要两三百行代码了,并且其中还可能出错,又要改bug等等。效率就严重下降。对于依赖库若是都这么麻烦也就不会有人用了。git
那么如何判断一个依赖库是否须要使用注解呢?其实很简单,只要记住如下两点便可:github
这里透露一下,Android注解的本质是使用Java的反射机制,后续会详细说明
相信ButterKnife应该有接触过吧,没有的也不要紧,如今正是时候。下面咱们会本身实现BindView与OnClick注解,实现ButterKnife中的对应注解功能。那么我先来看下总体的项目架构segmentfault
经过项目图,咱们能够清晰的看到,主要分为三个部分api
为了帮助你们可以更轻松的理解Android注解,今天主要分析的就是butterknife-annotations这个注解库。带你们一块儿来声明注解变量。数组
为了要实现开源库butterknife相似的绑定id效果,这里咱们先定义一个BindView注解,具体以下:架构
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface BindView { @IdRes int[] value(); }
嗯,仍是很简单的对吧。也就5行代码解决BindView注解的定义。app
那么再来详细剖析这5行代码。函数
首先是第一行代码的Retention,看它的使用方式就能知道,它也是一个声明了的注解。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
经过源码咱们能够看出该注解只接收一个参数,该参数为RetentionPolicy类型。那么咱们在进一步深刻RetentionPolicy:
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
在这里咱们发现它实际上是一个枚举,在枚举中支持三个常量,分别为SOURCE、CLASS与RUNTIME。它们的区别主要是做用的周期范围,下面我再对这三个的做用进行翻译一遍:
因此它们的存在的生命时长为SOURCE < CLASS < RUNTIME。知道了它的做用范围以后,咱们在自定义注解时就要尽可能较小注解的做用范围,提升项目的编译与运行速度。
由于咱们的BindView注解只是为了进行Viwe的绑定,因此在编译以后就无需存在,因此这里就使用了CLASS来进行标明。
下面咱们在来看第二行代码,这里使用到了另外一个注解Target,咱们仍是来看下它的源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
能够看到注解的源码都很是简单,这里接收了一个ElementType数组参数,ElementType不难猜出它的类型也是一个枚举:
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
ElementType中虽然有10常量,但咱们实际真正经常使用的也就是前面8种。它们表明自定义的注解可以做用的对象。分别为:
结合咱们的BindView的做用是对View进行id绑定,天然是做用与声明的字段上。因此在BindView中使用了FIELD。
再来看第四行代码
@IdRes int[] value()
有了上面的注解接触,不难理解这是标明BindView将接收一个int类型的数组参数。对于开源库butterknife中的BindView是接收须要绑定的View的id,这里咱们作一个改版,再接收一个String的id,用来为绑定的View设置默认值。这样咱们自定义了的BindView注释就完成了。
下面咱们再自定义一个OnClick点击的注解,通过上面的分析,能够在脑海中想一想Retention与Target分别什么值?
想好了以后咱们在来过一遍
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface OnClick { @IdRes int value(); }
Retention的做用范围与BindView同样首页SOURCE,在编译以后就无需存在;Target的做用对象与BindView不一样,既然是点击事件的点击操做,天然是做用在操做逻辑的方法上,因此这里使用METHOD。
文章开头有说起到本质是经过注解来自动生成代码,为咱们建立所需的类,那么在实际开发中一旦咱们的项目混淆了,这将会致使自动建立的类失效,从而致使咱们自定义的注解失效。因此为了防止其失效,咱们在这里再定义一个注解keep:
@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Keep { }
Retention的做用范围是在class文件中还要可以被其它class调用,因此这里使用CLASS;Target做用对象是自动生成的类,因此使用TYPE。至于参数则没必要要,它只是为了标明类,防止其被混淆。
butterknife-annotations库中的自定义注解就完成了。经过上面的分析,咱们注意点主要归结于如下三点:
注解变量的定义就到这结束了,同时文章中的代码均可以在Github中获取到。使用时请将分支切换到feat_annotation_processing
若是感受不错的话,能够点赞收藏哦,这是对我最大的支持,谢谢!