zoukankan      html  css  js  c++  java
  • Android -- Annotation(注解)原理详解及常见框架应用

    1,我们在上一篇讲到了EventBus源码及3.0版本的简单使用,知道了我们3.0版本是使用注解方式标记事件响应方法的,这里我们就有一个疑问了,为什么在一个方法加上类似于“@Subscribe”,就可以让我们的反射找到我们的事件响应方法。而且使用过BufferKnife、Dagger、Retrofit的同学或常见“@XXX”这种关键字 。so,抱着弄懂一切不明真相的精神,我们开始了这篇文章的探索。

    2,什么是注解?

      它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。

      ok,知道大家对上面的注解定义现在是一脸懵逼,所以举一个创建的栗子吧,“@Override”关键字我们熟悉吧,我们创建Activity的时候,重写onCreate方法,方法上面就有这个标记,拿我们看一下它的源码是什么

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

      "Target"、"Rentention" 这些什么是一些什么啊 ,感觉懵逼加重。如果我们自己写一个方法,然后在上面加上一个"@Override",看一下效果

      看到没,直接就报错了,所以我们在想,到底什么时候才能使用“@Override”关键字?能不能使用类似于“A”、“B”关键字?拥有这些标记的方法、属性、类有什么用啊?所以带着这些问题我们先来了解简单的常理知识。

    • 元注解

      看到这个名字可能同学们都有点蒙了,学过Python的同学肯定知道元数据。但是这里和我们的元数据没有一毛钱关系,定义也很简单,"Java中提供了四个元注解,专门注解其它注解。" 

       @Documented –注解是否将包含在JavaDoc中
       @Retention –什么时候使用该注解
       @Target –注解用于什么地方
       @Inherited – 是否允许子类继承该注解
    

      咦,看着上面的Retention 、Target  这不是我们"@Override"关键字中见过吗,哦,现在知道了它们对应的是什么意思了,让我们具体的看一下四个元注解的详细信息吧

    • @Documented
    是否会保存到 Javadoc 文档中
    

      这个很简单,我们看看EventBus3.0的@Subscribe的源码

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Subscribe {
        ThreadMode threadMode() default ThreadMode.POSTING;
    
        /**
         * If true, delivers the most recent sticky event (posted with
         * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
         */
        boolean sticky() default false;
    
        /** Subscriber priority to influence the order of event delivery.
         * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
         * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
         * delivery among subscribers with different {@link ThreadMode}s! */
        int priority() default 0;
    }
    

      果然,这里使用了我们的@Documented元注解了,我们继续往下看

    • @Inherited
    是否可以被继承,默认为 false
    

      我们这时候有个疑问,当我们的这个字段值为true的时候,并修饰的我们的class类表示的什么。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    • @Target 

      这个注解表示注解的作用范围,主要有如下:

    ElementType.FIELD 注解作用于变量
    
    ElementType.METHOD 注解作用于方法
    
    ElementType.PARAMETER 注解作用于参数
    
    ElementType.CONSTRUCTOR 注解作用于构造方法
    
    ElementType.LOCAL_VARIABLE 注解作用于局部变量
    
    ElementType.PACKAGE 注解作用于包
    

      而我们常见的基本上适用于变量、方法,这里给大家举例一下Retrofit2.0中的"@Query"注解,这是使用的就是PARAMETER 作用于参数,源码如下:

    @Documented
    @Target(PARAMETER)
    @Retention(RUNTIME)
    public @interface Query {
      /** The query parameter name. */
      String value();
    
      /**
       * Specifies whether the parameter {@linkplain #value() name} and value are already URL encoded.
       */
      boolean encoded() default false;
    }
    
    • @Retention 

      这个表示注解的保留方式,具体有一下三种类型

      RetentionPolicy.SOURCE : 只保留在源码中,不保留在class中,同时也不加载到虚拟机中 。在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。
      RetentionPolicy.CLASS : 保留在源码中,同时也保留到class中,但是不加载到虚拟机中 。在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
      RetentionPolicy.RUNTIME : 保留到源码中,同时也保留到class中,最后加载到虚拟机中。始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
    

      ok,把以上四种元数据的概念都弄懂了之后,我们在看我们之前看的"@Override"源码

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

      表示该注解作用于方法,且只保存在源码中。

      ok,再看一下我们Java中常见的注解有哪些吧,这里只用了解了解

    @Override: 表示该方法是重写父类中的方法,编译的时候会检查该方法,如果这个方法不是父类中存在的将会报错
    @Deprecated: 表示该方法时已经过时的,表示该方法有风险或者有更好的替代方法
    @SuppressWarnings: 表示在编译的时候忽略某种错误,如版本检查等

      这里还有一个疑问,我们的程序是怎么知道某个类中包含注解方法的呢?又是在获取注解方法中的相应属性的呢?

      这里我们使用反射来拿到class中的注解方法,主要使用这句关键代码

    method.getAnnotation(XXXAnnonation.class)

    3,自定义注解

      我们上面了解了一系列注解知识,现在我们想自定义自己的注解,作用于方法,注解里面的属性有name和id,所以我们代码如下:

    package com.qianmo.eventbustest;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by wangjitao on 2017/4/14 0014.
     * E-Mail:543441727@qq.com
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnonation {
        String name() default "";
    
        int id() default 0;
    }
    

      然后在Activity中标记我们自己编写的方法,设置annonation的属性

      @TestAnnonation(name = "wangjitao", id = 1)
        public void testMothed() {
            tv_message.setText(name + ":" + id);
        }
    

      提供反射的方法拿到Annonation中对应的属性,保存数据

     private void getAnnonationData(Class clazz) {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                TestAnnonation testAnnonation = method.getAnnotation(TestAnnonation.class);
                if (testAnnonation != null) {
                    name = testAnnonation.name();
                    id = testAnnonation.id();
                }
            }
        }
    

      完整代码如下:

    public class MainActivity extends AppCompatActivity {
        private Button btn_skip;
        private TextView tv_message;
        private String name;
        private int id;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btn_skip = (Button) findViewById(R.id.btn_skip);
            tv_message = (TextView) findViewById(R.id.tv_message);
            btn_skip.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    testMothed();
                }
            });
    
            getAnnonationData(MainActivity.class);
        }
    
        @TestAnnonation(name = "wangjitao", id = 1)
        public void testMothed() {
            tv_message.setText(name + ":" + id);
        }
    
        private void getAnnonationData(Class clazz) {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                TestAnnonation testAnnonation = method.getAnnotation(TestAnnonation.class);
                if (testAnnonation != null) {
                    name = testAnnonation.name();
                    id = testAnnonation.id();
                }
            }
        }
    
    }
    

      效果如下:

      ok,今天就给大家普及到这里,搞懂了以上的注解知识之后,我们看Retrofit2.0的 @GET、@POST等还有难度吗?  小伙子们赶紧去看看来弄懂之前没搞懂的注解原理吧。。。。

  • 相关阅读:
    .NET 世界中的远程脚本
    操作Active Directory C#
    三层体系结构总结(四)
    C#+Asp.net系列教程
    勿重复检测浏览器
    事件模块的演变(8)
    读jQuery之三(构建选择器)
    读jQuery之二(两种扩展)
    新API解析JSONAjax之七
    JS Queue LazyLoad 之二
  • 原文地址:https://www.cnblogs.com/wjtaigwh/p/6708935.html
Copyright © 2011-2022 走看看