美文网首页retrofit
Java注解-----编译时(MVVM)和运行时(Retrofi

Java注解-----编译时(MVVM)和运行时(Retrofi

作者: 文艺咖 | 来源:发表于2018-03-22 17:34 被阅读0次
    封面图.jpg

    前言

      继前一节内容,我们知道动态代理其实质就是依靠反射来实现的,这节讲述的是反射的另一个分支 注解

    反射与注解的关系

      关于反射与注解的关系,总结起来一句话就是:Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。

    注解的分类

    注解.PNG
    • 是否包含成员变量
      1. 标记注解: 没有成员变量的Annotation被称为标记。这种Annotation仅用自身是否存在,来为我们提供信息。例如@Override等。
      2. 元数据注解: 包含成员变量的Annotation。因为它们可以接受更多的元数据,因此被称为元数据Annotation。成员以无参数的方法的形式被声明,其方法名和返回值定义了该成员变量的名字和类型。

    • 来源
      1. 内建注解(java.lang包下): 是JDK自带的注解,其大致分为5种(后2种可忽略)
        @Override: 重写,标识覆盖它的父类的方法
        @Deprecated: 已过期,表示方法是不被建议使用的
        @SuppressWarnings: 压制警告,抑制警告
      2. 元注解(java.lang.annotation包下): 元注解就是修饰注解的注解。
        @Target: 表示该注解用于什么地方,可取的值包括:
          ElemenetType.CONSTRUCTOR: 构造器声明
          ElemenetType.FIELD: 域声明(包括 enum 实例)
          ElemenetType.LOCAL_VARIABLE: 局部变量声明
          ElemenetType.METHOD: 方法声明
          ElemenetType.PACKAGE: 包声明
          ElemenetType.PARAMETER: 参数声明
          ElemenetType.TYPE: 类,接口(包括注解类型)或enum声明
          ElemenetType.ANNOTATION_TYPE: 注解
        @Retention: 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
          RetentionPolicy.SOURCE: 注解将被编译器丢弃
          RetentionPolicy.CLASS: 注解在class文件中可用,但会被VM丢弃
          RetentionPolicy.RUNTIME: JVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
        @Documented: 将此注解包含在 javadoc 中
        @Inherited: 允许子类继承父类中的注解
      3. 自定义注解

    • 运行机制
      1. 源码注解(RetentionPolicy.SOURCE): 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
      2. 编译时注解(RetentionPolicy.CLASS): 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
      3. 运行时注解(RetentionPolicy.RUNTIME): 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

    自定义注解

    创建注解.png

    注解的使用,如下图:


    注解使用.PNG

      上图中,自定义注解已被创建使用,那么这个自定义注解有什么作用呢?

    自定义注解的作用往往体现在两个方面:
    1. 判断注解自身是否存在,来提供信息
    2. 获取注解中的元数据

    如果想实现注解的这两方面作用,就必须要先获取注解对象。

      那么,如何获取注解对象呢?

      对于大多数开发者来说,提及如何获取注解对象,第一个想到的就是通过 反射
      正如之前所说的,Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。
      但是往往使用反射,会对性能有造成影响。因此,我们也可以采取另外一种方法 编译时注解

    获取注解对象

    上面说过,获取注解对象大致可以分为两种方式,即

    • 运行时注解: 通过 反射 机制获取注解对象
    • 编译时注解: 通过 APT 方式获取注解对象

    误区: 关于注解这里,容易出现一个误区。一谈到注解,往往会说不要使用或尽量少些使用注解,因为注解会损耗手机性能。其实,这里提及的注解,往往就属于通过反射获取的运行时注解。

    • 同上面描述,使用注解方式有两种,通过反射来获取注解信息会对性能造成影响,而编译时注解就不一样了。编译时注解,是在 java 编译生成 .class 文件这一步进行的操作,性能问题也就无从说起了。因此,关于注解影响性能,并不能一概而论。
    • 即使是运行时注解,完全依赖于反射。虽然通过反射的方式会对性能造成影响,但是其实影响的因素往往也可以忽略不计的。

    1. 运行时注解

      public class MainActivity extends Activity {
          @ButterView(getResourceId = R.id.btn1)
          private Button btn1;
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              setView();
          }
    
          private void setView() {
              try {
                  Class c = this.getClass();
                  for (Field f : c.getDeclaredFields()) {
                      BindView bindView = f.getAnnotation(BindView.class);
                      if (bindView != null) {
                          f.setAccessible(true);
                          f.set(this, this.findViewById(bindView.getResourceId()));
                      }
                  }
              } catch (IllegalAccessException e) {
                e.printStackTrace();
              }
          }
      }
    

      上面的例子,有点类似于ButterKnife。ButterKnife框架中提供了很多的注解,上述代码仿照的是其中注解之一的,@BindView (控件id 注解)。这里仅仅是反射知识,不做详解,可以去前几章回顾下。像采用运行时注解的开源框架有 Retrofit ,它的底层采用动态代理获取接口函数,然后获取对应的注解来实现。
      不过,真正的ButterKnife框架采取的不是这种运行时注解,它底层是采用编译时注解来完成。
    类似采用编译时注解,我们熟知的开源框架还有 MVVMEventBus 等。

    2. 编译时注解
      在进入到编译时注解前,回顾下MVVM框架在android应用中是如何实现的呢

    MVVM_1.png
    MVVM_2.png
      使用过MVVM框架的同学应该了解,在android开发中,MVVM框架是采取Databinding来实现的。在图2中,ActivityMainBinding是根据activity_main.xml生成的,其命名规则为:xml文件名称 + Binding,首字母大写。如果中间被 “-” 分隔,那么分隔后的首字母也大写。 然而ActivityMainBinding的生成需要人为手动rebuild生成,其底层就采取的是编译时注解方式来实现,生成路径在build文件夹下
    build路径.png
      说了这么多,编译时注解具体该如何实现呢,这里需要先了解APT的概念。

      什么是APT

      APT是一种处理注解的工具,确切的说它是 javac 的一个工具,它用来在编译时扫描和处理注解,一个注解的注解处理器,以 java 代码(或者编译过的字节码)作为输入,生成 .java 文件作为输出,核心是交给自己定义的处理器去处理。

      编译期解析过程

      在某些代码元素上添加注解,在编译时编译器会检查 AbstractProcessor 的子类,并且调用该类型的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,使得开发人员可以在编译器进行相应的处理。

    编译时注解实战

    一般来说,搭建一个简单编译期注解,最终目标是 生成 .java 文件作为输出。

    编译时注解.png
    然而,中间有几个特别容易陷进去的坑,也是本人一步步踩过来的,今天总结列举出以下几点:

    1. 注解的生命周期
    2. 创建Java Library
    3. 定义 @SupportedAnnotationTypes 和 @SupportedSourceVersion(SourceVersion.RELEASE_7)
    4. javaCompileOptions 配置
    5. 生成 .java 文件路径
    6. META-INF中显示标识

    • 注解的生命周期
        首先要明确生命周期长度 SOURCE < CLASS < RUNTIME,虽然采用编译时注解时没问题,但是采用运行时注解(如果采用 RetentionPolicy.SOURCE),获取注解则会为空。

      注解.png
    • 创建Java Library
        若使用AndroidStudio(Eclipse同学自动忽略),需要注意Android Library并不是普通的JavaSE,所以并没有提供javax的一些功能,因此在新建Module的时候不能选Android Library而应该选Java Library。因为它只在编译的时候使用到JavaSE的功能,所以并不用担心在手机上出现异常。

    • 定义 @SupportedAnnotationTypes 和 @SupportedSourceVersion(SourceVersion.RELEASE_7)

      AbstractProcessor.png
      SupportedAnnotationTypes.png
        @SupportedAnnotationTypes:指定注解处理器是注册给那一个注解的,它是一个字符串的集合,意味着可以支持多个类型的注解,并且字符串是合法全名。
        @SupportedSourceVersion:指定Java版本。
    • javaCompileOptions 配置

      javaCompileOptions .png
        在app Module的build-gradle中加入javaCompileOptions 配置,否则编译会报错。
    • 生成 .java 文件路径
        .java 文件的输出路径格式为:包名.类名,一定要和文件内的包名和类名对应上。

      AbstractProcessor.png
    • META-INF中显示标识
        完成好了所有代码,踩过了所有的坑,然而点击rebuild后发现对应的.java文件并没有生成。那最后再检查下META-INF文件夹下,是否添加了javax.annotation.processing.Processor文件。它的作用是为了我们的AbstractProcessor内被使用。

      Processor_1.png
      Processor_2.png
        注意两点:
        1. Processor的存放路径千万别要写错。
        2. 自定义AbstractProcessor子类路径不要写错。
        这两点有一点不满足,那么.java文件就不会被生成。

    相关文章

      网友评论

        本文标题:Java注解-----编译时(MVVM)和运行时(Retrofi

        本文链接:https://www.haomeiwen.com/subject/cjxaxftx.html