android进阶---【注解(一)之运行时注解】

android进阶---注解

  • 注解
    • 1.什么是注解
    • 2.注解的产生
    • 3.注解的基础介绍
      • 3.1元注解
      • 3.2运行时注解与编译时注解区别
    • 4.自定义注解
      • 4.1自定义编写规则
      • 4.2自定义运行时注解

注解

注解这个概念,有些人可能会有些陌生。但是撸过代码的人应该都见过@Override,有用过ButterKnife,Glide,GreenDao等等这些框架,当不用再写那么多行的findViewById,不用再为图片三级缓存加载而挠头,不再为写一条复杂的sql语句而烦恼的时候,我们不觉得对这些框架竖起了大拇指,感觉他们就像神灵一般解救我们于水火,而不敢去了解和深入他们。但是,如果知道了他们的原理,你会发现,我们也可是是神灵的缔造者。

1.什么是注解

注解(Annotation),也叫元数据(即描述数据的数据),一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
说了一大堆,其实,就是比注释还牛逼的注释。
我们知道,注释是不会干预程序的执行的,注解同样不能影响代码的实际逻辑,但却可以起到辅助性的作用。

反正,就是很牛逼的。
比如:生成文档,像@param,@Document;编译检查,像Override;跟踪代码依赖性,替代配置文件,像eventBus,ButterKnife等。总之就是牛逼,就够了。

2.注解的产生

注解起源于java1.5,起初作用基本类似是加强型的注释语言,为了编译检查,增强可阅读性,比如@Override: 检查是否正确重写;@Deprecated: 表示该函数已弃用,会划一条横线;@NonNull: 检查参数非空。空指针可以在编译时发现,而不必等到运行时抛出NullPointerException。

java1.6 的时候,官方推出了一个插入式注解处理API(有兴趣可以看下java新特性有兴趣可以看下java新特性:https://blog.csdn.net/sysmedia/article/details/53608681)。指的注意的是,对注解的处理不仅运行时可以处理,还可以在编译期处理,通过注解处理器生成新的java代码并加入到编译中。
一句话: 我们能用java注解自动生成java文件了。 哎呦,这个屌!不错呦!

这里插播一句,著名的butterknife框架,在早期的时候,使用的应该是运行时注解,所以,在效率性能方面会有影响。但是后期,JakeWharton修改为编译期注解,这样对程序的运行就不存在性能问题了。(因为早期听别人说,butterknife会影响性能,所以一直没有用,常年来一直在不停的findViewById,在慢慢的了解了注解后,才知道,大神早已经把性能的问题解决了,我才放心大胆的用了)

3.注解的基础介绍

3.1元注解

元注解,其实就是修饰注解的注解。

看下@Override 的定义,咱们就知道了

package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override本身是一个注解,但是它的定义上还有 @Target@Retention 在修饰它,那么 像这两个用来 专门修饰注解的注解 就叫做元注解。

目前比较常见的元注解有:

  • @Retention:注解保留的生命周期
    参数 作用周期
    RetentionPolicy.SOURCE 只在源码中有效,编译时抛弃,如@Override
    RetentionPolicy.CLASS 编译期注解
    RetentionPolicy.RUNTIME 运行期注解

编译期注解 在class文件中可用,能够存于编译之后的字节码之中(编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。这些操作是通过注解处理器完成的。Java编译器集成了注解处理、通过在编译期间调用javac -processor命令可以调起注解处理器,它能够允许我们实现编译时注解的功能,从而提高函数库的性能。)该注解的注册信息会保留在.java源码里和.class文件里,在执行的时候,会被Java虚拟机丢弃,不会加载到虚拟机中。

运行期注解 VM在运行期也会保留注解,因此运行期注解可以通过反射获取注解的相关信息(运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答。)Java虚拟机在运行期也保留注解信息,可以通过反射机制读取注解的信息(.java源码,.class文件和执行的时候都有注解的信息)

  • @Target:注解对象的作用范围。

    参数 作用范围
    ElementType.TYPE 类、接口、枚举、注解类型
    ElementType.FIELD 类成员属性(构造方法、方法、成员变量)
    ElementType.METHOD 方法
    ElementType.PARAMETER 参数
    ElementType.CONSTRUCTOR 构造参数
    ElementType.LOCAL_VARIABLE 局部变量
    ElementType.ANNOTATION_TYPE 注解
    ElementType.PACKAGE
    ElementType.TYPE_PARAMETER 类型参数
    ElementType.TYPE_USE 类型使用说明
  • @Inherited:标明所修饰的注解,在所作用的类上,是否可以被继承。

注解所作用的类,在继承时默认无法继承父类的注解。除非注解声明了 @Inherited。同时Inherited声明出来的注解,只对类有效,对方法/属性无效。

  • @Documented:如其名,javadoc的工具文档化

如其名,javadoc的工具文档化,一般不关心。

  • @Repeatable:表示这个注解可以在同一个项上面应用多次(java8)

3.2运行时注解与编译时注解区别

运行时注解 一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简单。并不是说为了性能运行期注解就不能用了,只能说不能滥用,要在性能方面给予考虑。目前主要的用到运行期注解的框架差不多都有缓存机制,只有在第一次使用时通过反射机制,当再次使用时直接从缓存中取出。
编译时注解 能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。通过注解处理器完成对注解的解析。Java编译器集成了注解处理器,允许实现编译期注解功能,调高函数库性能。

编译期: 是指把源码交给编译器编译成计算机可以执行的文件的过程.在Java中也就是把Java代码编成class文件的过程.编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误.优化代码神马的。

运行期:是把编译后的文件交给计算机执行.直到程序运行结束.所谓运行期就把在磁盘中的代码放到内存中执行起来。

4.自定义注解

4.1自定义编写规则

  • 注解类型的声明,使用@interface 和元注解进行描述。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyTest {
    String log();
} 
  • 注解的元素是以 接口方法 的形式体现的(但它真的是元素),方法声明不能包含任何参数或 throws,只能用public或默认(default)访问权修饰。
    String log()只表示MyTest这个注解有一个元素叫 log

  • 方法的返回类型 必须是基本数据类型、String、Class、枚举、注解或这些类型的数组,方法可以有默认值(如果没有默认值,必须在属性中设置;Class 默认值不能为null) 如:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Hello {
    String name() default "";
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface ClickInterval {
    long value() default 1000;
}
  • 如果自定义注解只有一个参数,参数的名字如果是自定义那么,在调用时候,需要写上参数“名称=值”:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
     int id() ;
}

调用时:

@MyBindView(id = R.id.tv_test)
public TextView tv_test;

如果参数有且只有一个,且参数名字是value,则,调用时候,可不写参数名称;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
     int value() ;
}

调用时:

@MyBindView(R.id.tv_test)
public TextView tv_test;

4.2自定义运行时注解

学习自定义运行时注解前,最好先了解下反射的相关api;

自定义注解 + 注解处理器(会用到java反射机制)

很简单!来做一个弱智版的butterknife吧
先搞个自定义注解:

既然是运行时注解,那 @Retention 就选择 RetentionPolicy.RUNTIME

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
     int value() ;
}

再怼个注解处理器:

public class MyButterKnife {
    public static void bindView(Activity activity){
        Class<? extends Activity> clazz = activity.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println("名称: " + field.getName());
            if (field.isAnnotationPresent(MyBindView.class)) {
                MyBindView annotation = field.getAnnotation(MyBindView.class);
                if (annotation != null) {
                    try {
                        field.set(activity, activity.findViewById(annotation.value()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }else {
            }
        }
    }
}

调用,测试下。完事!

public class MainActivity extends AppCompatActivity {

    @MyBindView(R.id.tv_test)
    public TextView tv_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterKnife.bindView(this);
        Test();
    }

    public void Test() {
        tv_test.setText("哈哈哈哈");
    }
}

android进阶---【注解(一)之运行时注解】_第1张图片
但是,如果你想要更高、更快、更强的话,你就需要学会自定义编译期注解

毕竟这么简单的东西,拿出去装逼,有些丢人啊。
如果你想看更加有深度、有内涵、有料的东西,就看下一篇吧!

在此感谢以下文章的内容分享:
Android 自定义注解详解
Android 中优雅地使用注解
Android注解快速入门和实用解析

你可能感兴趣的:(android进阶)