前言
-
注解的底层也是使用反射实现的
,我们可以自定义一个注解来体会下。注解和接口有点类似,不过申明注解类需要加上@interface,注解类里面,只支持基本类型、String及枚举类型,里面所有属性被定义成方法,并允许提供默认值。 - 成员参数只能使用八种基本类型(byte、short、char、int、long、float、double、boolean)和String、Enum、Class、annotations等数据类型,及其数组。
- 使用带有属性的Annotation时,必须为其所有定义的属性指定值(使用default的可以不用指定)
- 定义Annotation时可以使用default关键字为属性设置默认值,使用时不为该属性指定值时会使用默认值
- 如果Annotation中具有名为value的属性,在使用时如果只使用value属性的话,可以不写属性名直接指
一、四大元注解
1)、@Target ——注解用于什么地方
ANNOTATION_TYPE,//给注解注解(这貌似把自己不当类来看)
ElementType.FIELD //注解作用于变量
ElementType.METHOD //注解作用于方法
ElementType.PARAMETER //注解作用于参数
ElementType.CONSTRUCTOR //注解作用于构造方法
ElementType.LOCAL_VARIABLE //注解作用于局部变量
ElementType.PACKAGE //注解作用于包
2)、@Retention — 注解运行状态
Java中
public enum RetentionPolicy {
/**
*源码状态运行,
*/
SOURCE,
/**
*编译类文件时运行
*/
CLASS,
/**
* 运行时运行
*/
RUNTIME
}
kotlin中
public enum class AnnotationRetention {
/** Annotation isn't stored in binary output */
SOURCE,
/** Annotation is stored in binary output, but invisible for reflection */
BINARY,
/** Annotation is stored in binary output and visible for reflection (default retention) */
RUNTIME
}
-
RetentionPolicy.SOURCE:
标记的注解仅保留在源码中,并被编译器忽略,注解信息会被丢弃,不会保留在编译好的class文件里。 -
RetentionPolicy.CLASS:
标记的注解在编译时由编译器保留在class字节码文件,在类加载的时被丢弃,运行时无法获取到,在java虚拟机(JVM)中忽略(Android 会在打包成dex的时候抛弃)。 -
RetentionPolicy.RUNTIME:
标记的注解由JVM保留,可以在运行时环境可以使用它,可以通过反射机制读取注解信息(源码、class文件和执行的时候都有注解的信息)。
总结: SOURCE < CLASS < RUNTIME, 既CLASS包含了SOURCE,RUNTIME包含SOURCU、CLASS
3)、@Documented — 生成说明文档,添加类的解释
表示注解会被包含在javaapi文档中
当一个注解被@Documented元注解所修饰时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {
}
这个元注解呗@Documented修饰,表示它本身会被文档化。@Retention注解的值RetentionPolicy.RUNTIME表示@Documented这个注解能保留在运行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented这个注解只能够来修饰注解类型
4)、@Inherited — 允许子类继承父类中的注解
允许子类继承父类的注解。
用于描述某个被标注的类型可被继承的,如果一个使用了@Inherited修饰的annotation类型类型被用于一个class,则这个annotation将被用于该class类的子类。
表明被修饰的注解类型是自动继承的。如果你想让一个类和它的子类都包含某个注解,就可以使用@Inherited来修饰这个注解。也就是说,假设@Parent类是Child类的父类,那么我们若用被@Inherited元注解所修饰的某个注解对Parent类进行了修饰,则相当于Child类也被该注解所修饰了。这个元注解的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited
public @interface MyAnnotation {
}
@MyAnnotation
public class MySuperClass {
}
public class MySubClass extends MySuperClass {
}
上述代码的大致意思是使用@Inherited修饰注解MyAnnotation使用MyAnnotation注解MySuperClass实现类MySubclass继承自MySuperClass
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
二、注解的运用场景
运行时注解(RUNTIME)
在程序运行期间,运用反射动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。 运用反射技术
字节码注解(CLASS)
在程序编译时,编译出CLASS后,通过修改CLASS数据已实现修改代码的逻辑目的。对于是否需要修改的区分或者要修改成不同逻辑的逻辑判断可以使用注解。 运用字节码增强(AspectJ技术实现)
大名鼎鼎的ButterKnife、ARouter运用的就是编译时注解。在我们编译时,就根据注解,自动生成了一些辅助类。
源码(SOURCE)
在编译期间能获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类,提供语法检查(IDE实现检测语法)。如:@IntDef,@DrawableRes。 运用APT技术
- 你得先依赖APT,然后自己写一个类继承AbstractProcessor,重写process方法,在里面实现如何把配置或注解的信息变成所需要的类。APT技术可以在SOURCE、CLASS、RUNTIME中使用。
三、自定义实现注解
1)、运行时注解(RUNTIME)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnonation {
String name() default "";
int Id() default 0;
}
注解关键字时@interface,然后上面标注为元注解,表示只能修饰方法并且加载到虚拟机中,里面时这个注解所具有的属性,name, id,我们在给方法加注解的时候设置相应的值。
@TestAnnonation(name = "android" , Id = 1)
private void testAnno(){
}
上面我们在一个方法上面添加注解,然后我们通过下面的方法将这个注解打印出来
private void outputAnnoDetail(Class clazz){
Method [] methods = clazz.getDeclaredMethods();
for(Method method : methods) {
TestAnnonation testAnnonation = method.getAnnotation(TestAnnonation.class);
if (testAnnonation != null) {
Log.d("anonation", "name------>" + testAnnonation.name() + "------>Id------>" + testAnnonation.Id());
}
}
}
2)、编译时注解(CLASS)
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
四、标准注解/内建注解
1、@Override注解
@Override注解用来修饰对父类进行重写的方法。如果一个并非重写父类的方法使用这个注解,编译器将提示错误。
public class MySuperClass {
public void doTheThing() {
System.out.println("Do the thing");
}
}
public class MySubClass extends MySuperClass{
@Override
public void doTheThing() {
System.out.println("Do it differently");
}
}
2、@ Deprecated
@Deprecate 标记类、方法、属性,如果上述三种元素不再使用,使用@Deprecated注解,建议用户不再使用。
@Deprecated
public class MyComponent {
}
3、@SuppressWarnings
@SuppressWarnings 用来抑制编译器生成警告信息。可以修饰的元素为类,方法,方法参数,属性,局部变量。
@SuppressWarnings
public void methodWithWarning() {
}
五、Android注解框架 ButterKnife
- ButterKnife注解框架是大家常用的注解框架,它主要作用是绑定View并且绑定View常用的监听事件,下面是其中一个注解
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
- 通过上面的代码可以看出,ButterKnife的注解保留方式为CLASS模式,也就是会保留到class中但是不会背加载到虚拟机中,这个时候我们就要看下它的AbstractProcessor,一般标注为Class的都会重写AbstractProcessor类,这样在虚拟机进行编译的时候就会做相应的处理。
主要看一下几个方法:
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
上面的方法主要表明会处理哪些注解
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return true;
}
- 然后就是在编译的时候的具体处理过程,这个过程主要时先找到并解析注解,然后生成java文件,这样在虚拟机真正执行的时候就不用去查找和解析,也就不会耗时了。
- EventBus是使用运行时注解,主要的作用是在运行的时候会去查找所有被注解的方法,然后再去解析注解。运行时注解会影响程序的性能,毕竟在运行的时候有一个查找的过程,所以运行时注解的作用一般是标记一个作用区。
五、注解基础知识思维导图
六、实战
1)用RUNTIME注解模仿ButterKnife
1、定义注解
- 作用字段:减少 view 的 findViewById 使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectVariate {
@IdRes int value();
}
- 作用字段:直接给参数复制使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectValue {
String value() default "";
}
2、工具类
public class InjectUtils {
public static void init(Activity activity) {
InjectUtils.initVariateControl(activity);
InjectUtils.initValueControl(activity);
}
/**
* 减少 view的 findViewById 使用
*
* @param activity
*/
public static void initVariateControl(Activity activity) {
Class<? extends Activity> aClass = activity.getClass();
//获取此类所有成员
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
//判断此类是否是 ControlAnnotation 注解
if (field.isAnnotationPresent(InjectVariate.class)) {
InjectVariate annotation = field.getAnnotation(InjectVariate.class);
//获取注解中设置的ID
int value = annotation.value();
//通过ID获取view
View view = activity.findViewById(value);
//设置可以访问私有类型
field.setAccessible(true);
try {
//第一个参数是,着那个对象上去设置属性/调用方法。
//static 方法/属性时,第一个参数可为空。因为 A.i, new A().i;
//可以看到static 是可以直接调用,非static是必须new一个对象才能调用。
field.set(activity, view); //设置值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 用于Activity 的获取值
* 用Intent设置数据,都是保存在Bundle中;获取也是从 Bundle中根据key获取。
* @param activity
*/
public static void initValueControl(Activity activity) {
Class<? extends Activity> aClass = activity.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field filed : declaredFields) {
if (filed.isAnnotationPresent(InjectValue.class)) {
InjectValue annotation = filed.getAnnotation(InjectValue.class);
String value = annotation.value();
filed.setAccessible(true);
Bundle extras = activity.getIntent().getExtras();
if (extras == null) {
return;
}
String key = value.isEmpty() ? filed.getName() : value;
if (extras.containsKey(key)) {
Object obj = extras.get(key);
//获取数组单个元素类型
Class<?> componentType = filed.getType().getComponentType();
//当前属性数组是否是parcelable(子类)数组
if (filed.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)) {
Object[] objs = (Object[]) obj;
//创建对应数组的object拷贝
Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) filed.getType());
obj = objects;
}
filed.setAccessible(true);
try {
filed.set(activity, obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
3、运用
public class MainActivity extends AppCompatActivity {
@InjectVariate(value = R.id.name_tv)
private TextView nameTv;
@InjectValue(value = "name")
private String name;
@InjectValue
private int age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
InjectUtils.init(this);
nameTv.setText(name + "=========" + age);
}
}