android开发教程:自定义注解之如何实现注解变量

​对Android注释有一点接触,但我相信大部分人在使用其他依赖库时都有所接触。因为有些库,如果你想用它,就必须用它提供的注释。例如:ButterKnife、Dagger2、Room等。

为什么要使用注释?用过的人应该都知道,最明显的就是方便简单。通过使用注释,可以帮助我们自动生成一些重复的代码,减轻我们在项目编译阶段的负担。典型的ButterKnife的本质就是使用Android注释,可以减少我们对view.findViewById的编译,提高我们的开发效率。

那么如何判断一个依赖库是否需要使用注解呢?其实很简单,只要记住以下两点即可:

  • 需要生成的代码不能与项目逻辑有关
  • Android注解只能生成代码,并不能修改代码

这里透露一下,Android注解的本质是使用Java的反射机制,后续会详细说明

项目架构

我相信ButterKnife应该和它有过接触。,没有也没关系。现在正是时候。接下来,我们将自己实现BindView和OnClick注释,并在ButterKnife中实现相应的注释功能。那我们先来看一下整体的项目架构。

通过项目图,我们可以清晰的看到,主要分为三个部分

  • 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:使用这个标明的注释将在编译时被记录在生成的类文件中,但在运行时将被VM丢弃。默认为这种模式。
RUNTIME:使用这种标明的注释将在编译时保存在生成的类文件中,在运行时保存在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是接收需要绑定的视图的id。这里我们做一个修改,然后接收一个字符串的id来设置绑定视图的默认值。这就完成了我们自定义的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: 防止后续自动生成的类被混淆

注解变量的定义就到这结束了。以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可加qq群:494807897一起学习交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值