Android-Annotation

什么是注解

  • 所有写过Java的都应该见过见过@Override@Deprecated这种“神奇的”语法吧,比如

    @Override
    public String toString() {
      ...
    }
    

    再比如说Android里面的ButterKnife也是通过这种方式来“自动”的完成View的查找,再比如说Retrofit也用到了这种语法,当我们用一些优雅的第三方库的时候,往往会用到注解来很方便的实现一些功能,模糊的去讲,注解是可以用到parameters、field、method、class、annotation上的一种特殊的语法,比如说在Retrofit里面就体现的淋漓尽致

    @GET("/get/")
    Call<Response<String>> getSomething(@Query String param);
    

注解之元注解

  • 元注解就是Java提供的为我们自定义注解而提供的内置注解,其实就相当于Java中的关键字,他们是内置的,有了这些关键字我们才能写自己的代码
  • 元注解有四个:
    • @Target
    • @Retention
    • @Inherited
    • @Documented
@Target
  • Target也就是注解可以修饰的类型,所有的类型都在ElementType下面,比如ElementType.CLASS代表可以修饰类、ElementType.TYPE代表可以修饰任何类型、ElementType.ANNOTATION_TYPE代表这个注解可以修饰注解,其他的可以自行去查看文档

  • 提前先说一下,每一个注解都可以定义一个value的属性,作为默认的注解值,另外在注解中如果使用数组类型,我们可以仅使用一个值,Java会自动把单个值包装成一个数组对象,例如

    @Target(ElementType.CLASS)
    public @interface MyAnnotation {}
    

    或者多个值

    @Target({ElementType.CLASS, ElementType.Field})
    public @interface MyAnnotation {}
    

    下面自定义还会细讲一些

@Retention
  • Retention表示了这个注解存在的时间,其所有类型在RetentionPolicy下,只有三种类型
    • SOURCE: 表示只在源码中存在,也就是说这个注解会在javac编译时抹去,它的作用就是在编译的时候做一些静态检查,比如@Override
    • CLASS: 表示会在class文件中存在,但是会被VM忽略,也就是说无法通过反射动态获取注解的值,这也是默认行为,我们可以从class文件中找到这些信息,也就意味着可以自定义类加载器,然后可以在原始IO中找到对应信息(ClassLoader.findClass),只是通过class文件在永生区/元空间生成class对象的时候,classLoader对应的jni方法defineClass(注:在Java8上响应的方法是defineClass0、defineClass1、defineClass2,在Android上是defineClass)会忽略这样的Annotation;同时,我们可以通过decompile获得这些注解信息
    • RUNTIME: 表示在运行时也会存在,也就是我们可以在代码中动态获取到这个注解和注解的值,当然获取的方式是通过反射
@Inherited
  • 如果定义注解时使用了Inherited,那么当我们使用这个注解去修饰类A的时候,即使我们不对A的子类使用注解,但是我们依旧能通过子类获取到这个注解,也就是说@Inherited意味着自定义的注解是可以继承的,当我们尝试从某个类拿Inherited的注解时,会不停的去它的superClass中寻找。但是记住这种可以继承的特性只对类生效,也就是说Override的方法是不生效的
@Documented
  • 如果使用@Documented修饰了某个自定义注解,那这个注解在生成Javadoc时会自动保留,即添加在对应的方法/变量/…上

使用注解

基础知识
  • 在注解中只能使用方法,但是注解的方法在使用的时候和普通字段一样
  • 注解中的value字段有特殊含义,是默认赋值字段
  • 定义注解时可以使用数组类型,而且当使用时可以只穿入单个值,java会自动把它包装成一个长度为1的数组
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
    int[] value();
    String name() default "";
}

public class Person {
    @MyAnnotation(1)
    int age;
    
    @MyAnnotation(name="John")
    String name;
}
编译时注解
  • 可以使用RetentionPolicy.SOURCE或者RetentionPolicy.CLASS来达到编译时注解的目的
  • 这种编译期的注解主要在于自动生成代码,比如Android上的ButterKnife
  • 编译期注解的优点在于消耗的时间完全在编译期,在运行时不需要额外的时间,但是缺点也是很明显的,首先因为要自动生成代码(元编程),编写这种代码其实还不如反射写起来舒服(因为反射的时候好歹可以直接获取到某个变量的值),其次就是很多情况下要求字段必须不能是private的,或者更确切的说生成的代码的位置必须是可以访问到注解所修饰的字段的,(参见ButterKnife),当然你也可以把生成的代码插入字段所在的源码文件中,就不用管它是不是private了
  • 栗子(我直接在Android Studio上做的实验,Intellij Idea没有找到怎么让AbstractProcessor生效):
//MyProcessor: 继承AbstractProcessor, 都是接口类型Processor, 这个类其实很好理解, 是java为我们提供
//的接口,当javac编译的时候会调用你配置好的Processor类去进行编译期注解的处理, 不然你只定义Annotation谁
//知道应该怎么处理它呢?所以我们必须要自己实现Processor,也叫注解处理器
//这个类做的事情其实是生成StaticAnnotation修饰的<变量名字, 类名>的json
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        File file = new File("output.json");
        System.out.println("------------");
        try {
            for (TypeElement element : annotations) {
                System.out.print(element.getSimpleName() + " ");
            }
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(StaticAnnotation.class);
            System.out.println(elements.size());
            if (elements.size() == 0) return false;
            Writer writer = new BufferedWriter(new FileWriter(file));
            writer.append("{\n");
            for (Element element : elements) {
                System.out.println(element.getSimpleName());
                System.out.println(element.getKind() == ElementKind.FIELD);
                if (element.getKind() == ElementKind.FIELD) {
                    writer.append(element.getSimpleName());
                    writer.append(": ");
                    writer.append(element.getEnclosingElement().getSimpleName());
                    writer.append("\n");
                }
            }
            writer.append("}\n");
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    //声明可以处理的注解的类型,添加到set里的必须是全名
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(StaticAnnotation.class.getCanonicalName());
        return set;
    }
}

//自定义的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface StaticAnnotation {
    int value();
}

//测试类
public class Student {
    @StaticAnnotation(3)
    String name;

    @StaticAnnotation(2)
    boolean isMale;
}

注:

  • 在Android上使用时,AbstractProcessor在Android module下是获取不到的,要建立一个java module,然后在main下面建立META-INF/services/javax.annotation.processing.Processor,再向里面添加对应的Processor的全名
  • @SupportedAnnotationTypes就是对getSupportAnnotationTypes()的简写,@SupportedSourceVersion对应getSupportedSourceVersion()
  • 我个人认为很多情况下使用RetentionPolicy.SOURCE就已经足够了,毕竟很多情况下我们只是生成完代码之后就用不着注解了
运行时注解
  • Retention对应的就是RUNTIME,Retrofit就是使用的这种注解,运行时注解在VM中也会保留,所以我们可以通过反射方法拿到Annotation(Method、Field、Class都有getAnnotation方法)
  • 运行时的注解就不像编译时的那么麻烦了,因为它不需要Processor这种"驱动",它的优点就是灵活,缺点就是反射导致效率低,不过在网络请求中,反射效率再怎么低都是要比网络延迟要好很多的
  • 栗子
public class TestClass {
    @MyAnnotation(1)
    private int value;

    public static void main(String[] args) {
        TestClass instance = new TestClass();
        Class clazz = instance.getClass();
        try {
            Field field = clazz.getDeclaredField("value");
            MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
            Object o = field.get(instance);
            o = annotation.name();
            field.setInt(instance, annotation.name()[0]);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println(instance.value);
    }
}

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    int[] name() default 0;
    int[] value();
}

上述例子利用注解对相应的实例变量进行了赋值,虽然反射也很烦,但是还是要比编译时生成代码写起来要更舒服一些的(也许是我对反射比元编程更习惯一些吧)

End

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,149评论 0 2
  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 29,131评论 15 116
  • 一、Annotation简单介绍 AndroidAnnotations是一个能够让你快速进行Android开发的注...
    黄海佳阅读 4,435评论 0 2
  • 写在前面的话 之前这块知识是缺失的,趁今天有时间,先补一补。第一次接触到butterKnife的时候,就发现注解是...
    满月写阅读 1,961评论 0 5
  • 大地奉献了胸脯 把生机留给了绿色 那孤独的影子熄灭了 远方的灯火 帆在动 船在动 白云在动 心在动 湖面下沉睡的鱼...
    MMXI阅读 160评论 0 0