一、什么是注解?
注解是一种元数据, 可以添加到java代码中。类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。注解并没有什么魔法, 之所以产生作用, 是解析注解后做了相应的处理。注解仅仅只是个标记罢了。
例如@Override就是注解,它的作用是:
1、检查是否正确的重写了父类中的方法。
2、标明代码,这是一个重写的方法。
注解使用@interface定义,@Override的定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
二、元注解
1、设么是元注解
Java内置的注解有Override, Deprecated, SuppressWarnings等, 作用相信大家都知道。
Override注解的源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Override注解上面有两个注解, 这就是元注解。元注解是用来定义注解的注解,其作用是定义注解的作用范围, 使用在什么元素上等, 下面来详细介绍。
2、元注解分类
元注解共有四种@Retention、@Target、 @Inherited,、@Documented。
1)、@Retention
保留的生命周期,取值为RetentionPolicy的枚举,默认值为CLASS.。可选值有三种
SOURCE: 只在源码中有效,编译时抛弃,如上面的@Override
CLASS: 编译class文件时生效(编译时注解)
RUNTIME: 运行时才生效(运行时注解)
2)、@Target
可以用来修饰哪些程序元素,取值为ElementType枚举。未标注则表示可修饰所有。
TYPE:类、接口、枚举、注解类型。
FIELD:类成员(构造方法、方法、成员变量)。
METHOD:方法。
PARAMETER:参数。
CONSTRUCTOR:构造器。
LOCAL_VARIABLE:局部变量。
ANNOTATION_TYPE:注解。
PACKAGE:包声明。
TYPE_PARAMETER:类型参数。
TYPE_USE:类型使用声明。
3)、@Inherited
注解自定义的注解。在继承时默认无法继承父类的注解,除非注解声明了 @Inherited
4)、@Documented
是否会保存到 Javadoc 文档中
三、自定义注解基础
1、定义注解
定义新的注解使用@interface关键字,定义注解和定义接口方式很像,通用格式如下:
public @interface 注解名 {
方法参数
}
定义一个简单的注解如下:
public @interface Testable {
}
定义了注解后,可以在程序任意位置使用该注解。通常可用于修饰程序中的类、方法、变量、接口等。一般会把注解放在所有修饰符前面,并且注解可能需要为成员变量指定值,注解的长度可能较长,所以通常把注解单独放一行。如下:
@Testable
public class MyTest {
}
2、为注解添加成员变量
注解还可以带成员变量,成员变量以无形参的方法形式来声明。方法的返回值类型定义成员变量的类型,方法名定义成员变量的名字。使用default关键字指定成员变量的默认值。成员变量定义如下:
public @interface GetViewTo {
int value() default -1;
}
value就是成员变量,默认值时-1
使用注解GetViewTo:
@GetViewTo(R.id.btn_one)
Button btnOne;
注意变量名是value使用时可以在括号中直接填入变量值即可。如果变量名是其他的,就需要指定变量名。
定义注解User
public @interface User {
String name();
int age();
}
使用注解User
@User(name = "小王", age = 18)
String user;
name和age的先后顺序可以互换,但是必须指定变量名name和age
四、自定义注解
1、自定义运行时注解
该注解的作用是在运行时获取控件实例。
1)、定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetViewTo {
int value() default -1;
}
指定注解的的对象的类成员,该注解在运行时有效。注解中有一个成员变量value,用于保存控件的id。
2)、使用注解
@GetViewTo(R.id.btn_one)
Button btnOne;
但是这样并不能实现获取控件id的功能,定义注解主要的工作时如何通过注解实现想要的功能,一般通过反射实现。
3)、在运行时通过反射获取控件的id
private void getAllAnnotationView() {
//获得成员变量
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
//判断该成员是否有注解
if (field.getAnnotations() != null) {
//确定注解类型
if (field.isAnnotationPresent(GetViewTo.class)) {
//允许修改反射属性
field.setAccessible(true);
GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, findViewById(getViewTo.value()));
}
}
} catch (Exception e) {
}
}
}
2、自定义编译时注解
运行时注解一般要使用反射,反射的效率比较低,为了提供效率我们可以使用编译时注解。
编译时注解要使用注解处理器AbstractProcessor。一般第三方注解相关的类库,如BufferKnike、ARouter都有一个Compiler命名的Module,这里面一般都是注解处理器,用于编译时处理对应的注解。
注解处理器(Annotation Processor)是Javac的一个工具,它用来在编译时扫描和处理注解。你可以自定义注解,并注册相应的注解处理器,用于处理你的注解逻辑。
下图就是ButterKnife中源码项目结构图,箭头指向的Module就是定义注解处理器的。
具体如何自定义编译器注解见博客Android使用注解处理器实现ButterKnife