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原理和源代码的文章了,这里就不多废话了,本文就到这里了,水平有限,不当之处请指出,谢谢。