Android 进阶-注解(Annotation)

1.了解注解

解析:注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响.  在Java文档中的定义如下:

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

简单来说,在Android中主要用途有:

1.起标识的作用,和Android Studio一起提示警告信息,可以快捷、安全有效地编写代码;

2.运行时注解:指的是运行阶段利用反射,动态获取被标记的方法、变量等,如EvenBus。

3.编译时注解:指的是程序在编译阶段会根据注解进行一些额外的处理,如ButterKnife。运行时注解和编译时注解,都可以理解为通过注解标识,然后进行相应处理,两者的区别是:前者是运行时执行的,反射的使用会降低性能;后者是编译阶段执行的,通过生成辅助类实现效果。

注意:注解本身不会影响代码的运行,以ButterKnife为例,注解之所以生效是因为ButterKnife是在标识后内部进行了处理。

下面先看看第一种用途:

2.注解的基本使用

Java内置的注解有Override, Deprecated, SuppressWarnings,我们都知道Override是用来标识重写的,Deprecated是标识废弃的,SuppressWarnings是告诉编译器忽略指定的警告的。而Android中,在android.support.annotaion包下定义了很多注解(数了一下,目前共有44个),下面就常见的注解举例说明:

1.@NonNull,一般用来标识不为空。


上图中,用@NonNull标识参数temp不为null,当在方法体中判断temp是否为null,编译器AS就会给出提示“temp不会是null的”。当我们在外部调用这个方法的时候,传入一个为空的参数时,AS也会给出提示。需要注意的一点是,AS不是什么时候都能检测出传入的参数可能为空的,例如经过较复杂的处理后,参数可能在过程中被设为空了。到这里不禁就要问了,既然没法百分百保证传入的参数不为空,那这个@NonNull的意义还大吗?笔者认为虽然不能百分百保证,但在大多数情况下,确实能帮助我们在编写代码的时候就趁早发现问题,当然在较复杂下,在外部调用的时候,当我们没法保证传入的值是否为空,还是有必要判断一下再调用这个方法。

2.资源注解:如@StringRes、@ColorRes、@StyleRes、@DrawableRes

定义一个方法,用@StringRes标识参数,

private String getContent(@StringResintresId){

return getString(resId);

}

当我们引用的时候,如果不小心传入了非String的资源id,如getContent(R.color.colorAccent),编译器就会报错“错误的资源类型”,如果没有使用注解,这个错误直到运行才会被发现,这些注解的使用能让我们避免或者更早发现问题。

以上是注解用途一的简单使用,在了解运行时注解和编译时注解前,我们需要先知道如何自定义注解,以及如何使用自定义的注解;

3.自定义注解

首先以Java中已有的注解@Override为例,说明一下是如何定义注解的,@Override的源码如下:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface Override{

}

可以看到,用关键字@interface声明注解,前两行的@Target和@Retention也是注解,准确来说是元注解(元注解就是用来定义注解的注解),@Target用来指定注解Overrider是用于修饰哪些元素,这里的@Target(ElementType.METHOD)具体指的是Override是用来修饰方法的,不是用来修饰字段的;@Retention是指定保留策略的,这里的@Retention(RetentionPolicy.SOURCE)具体指的是Override注解只有在源码中可用,在字节码或者运行时不可用,也就是说如果只需要在编写代码阶段注解生效,一般设置为@Retention(RetentionPolicy.SOURCE),如果希望在代码运行时注解生效,则需要设置为@Retention(RetentionPolicy.RUNTIME)。

现在对注解有了个大概的了解,那么我们开始仿照Override定义一个注解吧:

@Target(ElementType.METHOD)//指定该注解只能用于标识方法

@Retention(RetentionPolicy.SOURCE)//指定保留策略为在源码中可用

public @interface MyAnnotation{

//do nothing

}

如何使用?在方法前直接添加@MyAnnotation就可以了,到这里就完成了注解的定义和使用啦!

@MyAnnotation

private String getContent(int resId){

return getString(resId);

}

如果是在变量前添加这个注解,编译器是会报错的,因为我们指定了该注解只能用于标识方法!上面说了注解本身是不会造成影响的,所以这个MyAnnotation并没有什么卵用。

现在我们要实现这样一个功能:用MyAnnotation标识一个字段,并为这个字段设置一个默认值。来来来,修改一下MyAnnotation:

@Target(ElementType.FIELD)//指定用于标识字段

@Retention(RetentionPolicy.RUNTIME)//指定保留策略为运行时可用

public @interface MyAnnotation{

String value();   //用于保存默认值

}

当在注解中添加了value()方法后,那么使用的时候就从 @MyAnnotation 变成了 @MyAnnotation("Test Annotation"),没错,这里的value对应的就是“Test Annotation”,如下:

@MyAnnotation("Test Annotation")

String temp;

到这里,注解还是没有任何效果的,我们期望的是 temp的值就是“Test Annotation”,因此需要在初始化的时候做点事情,即把这个值“Test Annotation”赋给变量temp,这里使用反射来处理这个过程:

private void initMyAnnotation(Activity activity){

try{

//获取要解析的类

Class cls = Class.forName("com.example.test.Main3Activity");

//拿到所有Field

Field[] declaredFields = cls.getDeclaredFields();

for(Field field : declaredFields){

//获取Field上的注解

MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);

//如果这个Field有注解

if(annotation !=null){

//获取注解上的值

String value = annotation.value();

//将值设置给该字段

field.setAccessible(true);

field.set(activity,value);

}

}

}catch(ClassNotFoundException e) {

e.printStackTrace();

}catch(IllegalAccessException e) {

e.printStackTrace();

}

}

最后在activity的onCreate方法里加上这句:至此我们就完成了通过MyAnnotation为字段设置一个默认值的功能了。

initMyAnnotation(this);

以上就是注解在Android的第二种用途:运行时注解,也就是通过反射获得被注解的字段方法,然后再自己处理。

看到这里,是不是觉得我们也能实现类似于ButterKnife的功能啦(ButterKnife的原理不是使用反射的),说干就干:

首先定义一个注解ViewAnnotation:

@Target(ElementType.FIELD)  

@Retention(RetentionPolicy.RUNTIME)

public@interfaceViewAnnotation{

intvalue();

}

再声明一个方法处理赋值:

public static void bindView(Activity activity) {

Class aClass = activity.getClass();

//获取activity所有字段

Field[] fields = aClass.getDeclaredFields();

//得到被ViewInject注解的字段

for(Field field : fields) {

if(field.isAnnotationPresent(ViewAnnotation.class)) {

//得到字段的ViewInject注解

ViewAnnotationviewInject = field.getAnnotation(ViewAnnotation.class);

//得到注解的值

intviewId = viewInject.value();

//使用反射调用findViewById,并为字段设置值

try{

Method method = aClass.getMethod("findViewById", int.class);

method.setAccessible(true);

Object resView = method.invoke(activity,viewId);

field.setAccessible(true);

field.set(activity,resView);

}catch(NoSuchMethodException e) {

e.printStackTrace();

}catch(InvocationTargetException e) {

e.printStackTrace();

}catch(IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

最后记得在初始化时调用bindView(this)。

Ok,以上说的都是基于运行时注解的,开头说了,还有一种编译时注解,不得不再次祭出ButterKnife了,网上有很多剖析ButterKnife原理和源代码的文章了,这里就不多废话了,本文就到这里了,水平有限,不当之处请指出,谢谢。

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

推荐阅读更多精彩内容