这篇文章 2016年12月13日星期二 就写完了,当时想着等写完另外一篇关于自定义注解的一起发。结果没想到这一等就是半年多 - -。
有时候的确是这样啊,总想着等条件更好了再干,等准备完全了再开始,结果好多想法、好多事情都不了了之。
注解是一种元数据(描述数据的数据)
描述作用,不会直接生效,需要在编译前/运行时获取注解信息 代码检查
注解简单的说就是以 @ 开头的一个字符串,在 Android Studio 默认是黄色高亮,比如下面的 @Override:
这里的 @Override 没有值,只是一个修饰作用,告诉编译器这个方法要覆盖父类的方法,编译器会去检查父类有没有这个方法。
我们在使用注解时可以传入更详细的内容,使用 “key1=value1, key2=value2”的格式传入,比如:
@Author(name = "shixinzhang", date = "2016.12.13")
public class AnnotationTestActivity extends BaseActivity {...}
@Author 注解内部有两个字符串,分别为 name, date:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
String name();
String date();
}
当注解的属性只有一个时,可以命名为 value,这样在使用时可以使用快捷方式 – 直接传入值,而不是声明属性名,比如下面的 @ContentView,只有一个名称为 value() 的属性:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value();
}
使用时直接传参数,不必指明属性名:
@Author(name = "shixinzhang", date = "2016.12.13")
@ContentView(R.layout.activity_annotation)
public class AnnotationTestActivity extends BaseActivity {...}
上面的两个自定义注解用到了 @Retention, @Target,它们其实是 Java 内置的注解,方便我们使用。
Java 内置的注解主要有 9 个,分为位于 java.lang or java.lang.annotation 包下。
@SuppressWarning 支持的参数如下及使用方式见这篇 @SuppressWarning 使用及支持的参数.
修饰其他注解的注解称为“元注解”。
创建注解时,需要声明的类型为 @interface,看起来和接口有一些相似哈,其中的 @ 标明这是一个注解类型( “at ~ annotation type”):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value();
}
注解除了名字和接口有些相似,内容也很相似,都是声明一个方法,规定返回值,不同的是这里的方法其实是个属性,返回值规定了属性的类型(至于为什么要声明成方法而不是属性,可能是为了后续直接使用这个方法获取值比较直观吧)。
注意:如果你的注解中创建了多个属性,但是使用时只需要使用某几个,这时编译器会提示你有没有指明的属性。
我们可以使用 default … 为注解的某个属性指定默认值,这样即使不指定某个属性,编译器也不会报错。这通常可以节约很多时间,比如这样:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
String name() default "shixinzhang";
String date();
}
当我们使用 @Author 时没有指定 name = XXX,则会默认为 “shixinzhang”。
注解可以用来修饰类、方法、参数等等,具体的使用场景有以下三种:
注解处理器分为两种:
先介绍简单的一种:运行时注解处理器。
运行时注解需要使用 注解 + 反射 ,非常简单。
我们先自定义一个 ContentView
注解,表示当前布局对应的 layout 文件:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value() ;
}
然后用它修饰一个 Activity:
@ContentView(R.layout.activity_annotation)
public class AnnotationTestActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //调用父类的 onCreate
setContentView(R.layout.activity_annotation);
}
}
在 BaseActivity 中反射获取当前类使用的注解,拿到注解的值,就可以直接设置布局了:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
annotationProcess();
}
//读取注解,进行处理
private void annotationProcess() {
Class c = this.getClass();
//遍历所有子类
for (; c != Context.class; c = c.getSuperclass()) {
//找到使用 ContentView 注解的类
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try { //有可能出错的地方都要 try-catch
//获取 注解中的属性值,为 Activity 设置布局
this.setContentView(annotation.value());
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
这样就简单实现了运行时根据注解动态设置布局的功能。
黑科技、低性能
使用注解往往可以实现用非常少的代码作出匪夷所思的事情,比如 ButterKnife。
但被人诟病的是,运行时注解需要使用大量 Java 反射而引起较为严重的性能问题。
在使用运行时注解时需要小心,在调用方法时注意对异常的捕获,避免调用失败。
下一篇文章我们了解更为高性能的注解:使用编译时注解简单实现类似 ButterKnife 的效果。
https://docs.oracle.com/javase/tutorial/java/annotations/index.html
http://tutorials.jenkov.com/java/annotations.html
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html