对Android注释有一点接触,但我相信大部分人在使用其他依赖库时都有所接触。因为有些库,如果你想用它,就必须用它提供的注释。例如:ButterKnife、Dagger2、Room等。
为什么要使用注释?用过的人应该都知道,最明显的就是方便简单。通过使用注释,可以帮助我们自动生成一些重复的代码,减轻我们在项目编译阶段的负担。典型的ButterKnife的本质就是使用Android注释,可以减少我们对view.findViewById的编译,提高我们的开发效率。
用注释定义一个实体表,大概10行代码。如果要全部自己写,肯定要二三百行代码,可能会有错误,bug等等。效率严重降低。如果依赖库这么麻烦,就没人用了。
那么如何判断一个依赖库是否需要使用注解呢?其实很简单,只要记住以下两点即可:
需要生成的代码不能与项目逻辑有关
Android注解只能生成代码,并不能修改代码
这里透露一下,Android注解的本质是使用Java的反射机制,后续会详细说明
项目架构
相信ButterKnife应该有接触过吧,没有的也没关系,现在正是时候。下面我们会自己实现BindView与OnClick注解,实现ButterKnife中的对应注解功能。那么我先来看下整体的项目架构
android 自定义注解(android怎么注释代码)
主要分为三个部分
butterknife-annotations:注解库,包含BindView与OnClick等自定义的注解
butterknife-bind:绑定库,自定义的注解与声明的类绑定
butterknife-compiler: 解析编译生成库,解析声明类中的注解,在编译时自动生成相应的代码。
为了帮助大家能够更轻松的理解Android注解,今天主要分析的就是butterknife-annotations这个注解库。带大家一起来声明注解变量。
BindView
为了要实现开源库butterknife类似的绑定id效果,这里我们先定义一个BindView注解,具体如下:
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface
BindView { @IdRes int[] value(); }
嗯,还是很简单的对吧。也就5行代码解决BindView注解的定义。
那么再来详细剖析这5行代码。
Retention
首先是第一行代码的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:使用该标明的注解将在编译阶段记录到生成的class文件中,但在运行阶段时又会被VM抛弃。默认是该模式。
RUNTIME:使用该标明的注解将在编译阶段被保存在生成的class文件中,同时在运行阶段时会保存到VM中。所以它该注解将一直存在,自然能够通过java的反射机制进行读取。
所以它们的存在的生命时长为SOURCE < CLASS < RUNTIME。知道了它的作用范围之后,我们在自定义注解时就要尽量较小注解的作用范围,提高项目的编译与运行速度。
因为我们的BindView注解只是为了进行Viwe的绑定,所以在编译之后就无需存在,所以这里就使用了CLASS来进行标明。
Target
下面我们在来看第二行代码,这里使用到了另一个注解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种。它们代表自定义的注解能够作用的对象。分别为:
TYPE: 作用于类、接口或者枚举
FIELD:作用于类中声明的字段或者枚举中的常量
METHOD:作用于方法的声明语句中
PARAMETER:作用于参数声明语句中
CONSTRUCTOR:作用于构造函数的声明语句中
LOCAL_VARIABLE:作用于局部变量的声明语句中
ANNOTATION_TYPE:作用于注解的声明语句中
PACKAGE:作用于包的声明语句中
TYPE_PARAMETER:java 1.8之后,作用于类型声明的语句中
TYPE_USE:java 1.8之后,作用于使用类型的任意语句中
结合我们的BindView的作用是对View进行id绑定,自然是作用与声明的字段上。所以在BindView中使用了FIELD。
再来看第四行代码
@IdRes int[] value()
有了上面的注解接触,不难理解这是标明BindView将接收一个int类型的数组参数。对于开源库butterknife中的BindView是接收需要绑定的View的id,这里我们做一个改版,再接收一个String的id,用来为绑定的View设置默认值。这样我们自定义了的BindView注释就完成了。
OnClick
下面我们再自定义一个OnClick点击的注解,经过上面的分析,可以在脑海中想想Retention与Target分别什么值?
想好了之后我们在来过一遍
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface OnClick { @IdRes int value(); }
Retention的作用范围与BindView一样首页SOURCE,在编译之后就无需存在;Target的作用对象与BindView不同,既然是点击事件的点击操作,自然是作用在操作逻辑的方法上,所以这里使用METHOD。
keep
文章开头有提及到本质是通过注解来自动生成代码,为我们创建所需的类,那么在实际开发中一旦我们的项目混淆了,这将会导致自动创建的类失效,从而导致我们自定义的注解失效。所以为了防止其失效,我们在这里再定义一个注解keep:
@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Keep { }
Retention的作用范围是在class文件中还要能够被其它class调用,所以这里使用CLASS;Target作用对象是自动生成的类,所以使用TYPE。至于参数则不必要,它只是为了标明类,防止其被混淆。
总结
butterknife-annotations库中的自定义注解就完成了。通过上面的分析,我们注意点主要归结于以下三点:
Retention: 明确注解作用的生命时长,尽早的消除
Target: 明确注解作用的对象
Keep: 防止后续自动生成的类被混淆
注解变量的定义就到这结束了,同时文章中的代码都可以在Github中获取到。使用时请将分支切换到feat_annotation_processing
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以私信交流。
网友评论