Android-Annotation教你写自定义注解

5,833 阅读6分钟

一 前言

我相信注解我们多多少少的都会接触到,常用的框架Butterknife、Retrofit、ARouter等等都用到了注解,我想大家都会去搜一下什么是注解了吧。这里呢就以一个Demo去了解一下自定义注解的使用。

二 知识准备

我们最常见的注解莫过于@Override了吧,那我们就去看一下这个注解的代码。

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

好了就事论事我们先看下声明这个@interface乍一看还以为是interface呢不过这里多了个@那就是声明注解的关键字了,这只要记住就好了。大括号里面竟然没任何的定义,那就先放一放,我们来看一下其他的参数。

  • @Target()
    @Target说明了Annotation所修饰的对象范围,也就是我们这个注解是用在那个对象上面的:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
    作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)取值(ElementType)是来源于Java.lang.annotation.ElementType中的枚举类型元素:
       (1).CONSTRUCTOR:用于描述构造器
       (2).FIELD:用于描述域
       (3).LOCAL_VARIABLE:用于描述局部变量
       (4).METHOD:用于描述方法
       (5).PACKAGE:用于描述包
       (6).PARAMETER:用于描述参数
       (7).TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Retention()
    @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
    作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)取值(RetentionPoicy)来源于java.lang.annotation.RetentionPolicy的枚举类型值:
       (1).SOURCE:在源文件中有效(即源文件保留)
       (2).CLASS:在class文件中有效(即class保留)
       (3).RUNTIME:在运行时有效(即运行时保留)
  • @Documented
    @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
  • @Inherited:
    @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
这里呢大概的讲了一下定义在类上面的每个注解的意义和取值是什么,下面我们将进入我们的自定义注解中。

三 自定义注解

我们先来照葫芦画瓢,定义一个注解类

public @interface MyTag {

}

注解里面的定义也是有规定的:

  • 注解方法不能带有参数。

    • 注解方法返回值类型限定为:基本类型、String、Enums、Annotation或者这些类型的数组。
  • 注解方法可以有默认值。

  • 注解本身能够包含元注解,元注解被用来注解其他注解。

我们就来试一下吧!

public @interface MyTag {
  //声明返回值类型,这里可没有大括号啊,可以设置默认返回值,然后就直接";"了啊。
    String name () default "" ;

    int size () default 0 ;

}

定义好了注解我们就来规定我们这个注解要用到哪里何时用吧!

@Target({ElementType.METHOD,ElementType.FIELD})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
    String name () default "" ;
    int size () default 0 ;
}

这里呢我们定义这个注解可以用在属性和方法上面,是可继承的注解,可以出现在运行时的。因为我们这边要模仿一下一下其他注解框架中注解的用法,我这里才采用了RetentionPolicy.RUNTIME,因为在运行时我们采用反射可以得到里面的注解信息。
好了接下来看怎么使用我们的这个自定义的注解!

public class HomeActivity extends AppCompatActivity {
    @MyTag(name = "BMW",size = 100)
    Car car;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        //这里我们要首先注册一下这个类
        AnnotationCar.instance().inject(this);
      //当程序运行的时候这里将会输出该类Car的属性值。
        Log.e("WANG","Car is "+car.toString());
    }
}

是不是很像我们使用过的ButterKnife呢,这里呢我们再这个Activity里面定义了一个Car类的属性,然后再car这个变量上面定义我们的注解,并且给我们的注解赋值。然后我们再onCreate方法里面先初始化我们的注解,然后打印Car类的信息,先来看下结果吧

cn.example.wang.routerdemo E/WANG: Car is Car [name=BMW, size=100]

这样我们的自定义注解就有作用了,好了半天主要的代码就在那个初始化里面。

//自定义的类
/**
 * Created by WANG on 17/11/21.
 */

public class AnnotationCar {
    private static AnnotationCar annotationCar;
    public static AnnotationCar instance(){
        synchronized (AnnotationCar.class){
            if(annotationCar == null){
                annotationCar = new AnnotationCar();
            }
            return annotationCar;
        }
    }

    public void inject(Object o){
        Class<?> aClass = o.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field:declaredFields) {
            if(field.getName().equals("car") && field.isAnnotationPresent(MyTag.class)) {
                MyTag annotation = field.getAnnotation(MyTag.class);
                Class<?> type = field.getType();
                if(Car.class.equals(type)) {
                    try {
                        field.setAccessible(true);
                        field.set(o, new Car(annotation.name(), annotation.size()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

这就说明了为什么注解和反射是同时进入我们的知识圈里面的吧!这里呢我们先获取到类里面所有的属性,然后去找到被我们的注解MyTag修饰的那个属性,然后找到之后,先取我们注解里面的值,然后赋值给我们类里面的属性!这样我们就用注解去初始化了一个属性值,嘻嘻这里就结束了啊!

结束语

例子很简单,看完之后是不是也会写一个类似ButterKnife的效果了呢,有什么问题请留言给我,我会实时为你解答的!

感觉对你有帮助请点个赞啊~
欢迎关注 我的掘金
欢迎关注 我的简书
欢迎关注 我的csdn
源码下载