android注解和反射的理解
刚开始使用butterknife的时候,觉得太爽了,为啥加个@bindview就不用写烦人的 findViewById了呢?然后还有EventBus、Retrofit现在很多库都用到了注解, 现在来整理一下。还有处理运行时注解的反射也写在一起吧。
一、注解(Annotation)
1.基础概念
注解简单来说可以理解为代码里的特殊标记,这些标记可以在编译、类加载和运行时被读取,并进行相应的处理。例如最常见的override。
- 注解是一种元数据(描述数据的数据)。
- 注解是Java提供了一种源程序中的元素关联任何信息或者任何元数据的途径和方法。
- 注解是被动的元数据,注解没用主动行为。
注解的分类:
-
标准注解
标准注解主要有:
@Override:标记覆盖超类中的方法。
@Deprecated:对过时或不推荐使用的方法进行标记注解。
@SuppressWarnings:选择性的取消特定代码中的警告。
@SafeVarargs:用来声明使用了可变长度参数的方法,与泛型类一起使用时不会出现类型安全问题。 -
元注解
元注解是用来注解其他注解的注解,从而创建新的注解。主要有:
@Retention:注解保留的生命周期。
@Target:注解对象的作用范围。
@Inherited:表示注解可以被继承。
@Documented:表示这个注解应该被JavaDoc工具记录。
其中@Target和@Retention取值及说明见下表:
@Retention取值类型 | 说明 |
---|---|
RetentionPolicy.SOURCE | 源码注解。注解只保留在源码,编译后注解信息会被丢弃 |
RetentionPolicy.CLASS | 编译时注解。注解信息会保留在源码以及编译时.class中。运行时丢弃,不会保留在jvm |
RetentionPolicy.RUNTIME | 运行时注解。注解信息可以保留到运行时,可以通过反射获取注解信息 |
@Target取值类型 | 说明 |
---|---|
ElementType.TYPE | 能修饰接口、类、枚举 |
ElementType.FIELD | 能修饰成员变量 |
ElementType.METHOD | 能修饰方法 |
ElementType.PARAMETER | 能修饰参数 |
ElementType.CONSTRUCTOR | 能修饰构造方法 |
ElementType.LOCAL_VARIABLE | 能修饰局部变量 |
ElementType.ANNOTATION_TYPE | 能修饰注解 |
ElementType.PACKAGE | 能修饰包 |
ElementType.TYPE_PARAMETER | 类型参数声明 |
ElementType.TYPE_USE | 使用类型 |
2.自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
String name();
}
如上代码:自定义注解通过@interface关键字声明,修饰它的有两个元注解@Retention,@Target。根据上表可以知道此注解生命周期保留到运行时(RUNTIME),作用范围为成员变量(FIELD)。
注意:"String name()"并不是方法,注解中以“无形参的方法”来声明成员变量。也就是说该注解声明了一个名为“name”的String类型的成员变量。
另外@Targer有多个值时候也可以这样写@Target({type1,type2,type3,…})。
MyAnnotation中声明了name变量,使用时应该为该变量指定值:
@MyAnnotation(name = "李磊")
private String userName;
或者可以在定义注解时使用default关键字指定默认值,如下:
public @interface MyAnnotation {
String name() default "李磊";
}
这样的话就可以不必为name变量指定值:
@MyAnnotation
private String userName;
到这一步为止,我们自定义了注解、添加了默认值并添加在了userName上。但是此时打印出userName的值还是为null的,因为前面说到注解是被动元数据,不会有主动行为。所以我们要对注解进行处理才能获取到注解的值。
针对运行时注解和编译时注解分别会使用反射机制来处理和AbstractProcessor(apt)来处理。
二、反射(Reflection)
1.基本概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
2.常用方法
获取class的三种方法,这里以获取String类为例:
Class a=String.class; //1
//2
String s= "";
Class b=s.getClass();
//3
Class c=Class.forName("java.lang.String");//这里要填写完整的包名
获取到class后可以通过下面常用的一些方法获取该类的成员变量、方法、变量的类型、方法的返回值等数据:
-
Annotation[] getAnnotations () //获取这个类中所有注解
-
getClassLoader() //获取加载这个类的类加载器
-
getDeclaredMethods() //获取这个类中的声明的方法
-
getDeclaredFields() //获取这个类中的声明的变量
-
getReturnType() //获取方法的返回类型
-
getParameterTypes() //获取方法的传入参数类型
-
isAnnotation() //测试这类是否是一个注解类
-
getDeclaredConstructors() //获取所有的构造方法
-
getDeclaredMethod(String name, Class… parameterTypes)// 获取指定的构造方法(参数:参数类型.class)
-
getSuperclass() //获取这个类的父类
-
getInterfaces()// 获取这个类实现的所有接口
-
getFields() //获取这个类中所有被public修饰的成员变量
-
getField(String name) //获取指定名字的被public修饰的成员变量
-
isAnnotationPresent(Class<? extends Annotation> annotationType) //判断变量或方法是否被某注解修饰
-
getModifiers() //获得属性的修饰符,例如public,static等等
-
newInstance() //返回此Class所表示的类,通过调用默认的(即无参数)构造函数创建的一个新实例
3.示例
定义一个User类:
public class User {
private String userName;
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
反射获取User类中的成员变量、方法:
private void getUserClass() {
Class c = User.class;
Field[] fields = c.getDeclaredFields();
Method[] methods = c.getDeclaredMethods();
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(c.getModifiers()) + " class "); // 获取属性的修饰符,例如public,static等等
sb.append(c.getSimpleName() + " { \n"); //获取类名
for (Field field : fields) {
//遍历类中的变量
sb.append("\t");// 空格
sb.append(Modifier.toString(field.getModifiers()) + " ");
sb.append(field.getType().getSimpleName() + " ");// 属性的类型的名字 如String 、 int
sb.append(field.getName() + ";\n");
}
for (Method method : methods) {
//遍历类中的方法
sb.append("\t");
sb.append(Modifier.toString(method.getModifiers()) + " ");// 获取属性的修饰符
sb.append(method.getReturnType().getSimpleName() + " "); // 获取返回类型
sb.append(method.getName() + " "); //获取方法名
Parameter[] parameters = method.getParameters(); //获取方法所有参数
String params = "";
if (parameters.length > 0) {
//遍历参数
StringBuffer pSb = new StringBuffer();
for (Parameter parameter : parameters) {
//获取参数类型以及参数名称
pSb.append(parameter.getType().getSimpleName() + " " + parameter.getName() + ",");
}
//去掉最后一个逗号
params = pSb.substring(0, pSb.length() - 1);
}
sb.append("(" + params + ");\n");
}
sb.append("}");
System.out.println(sb);
}
方法逻辑比较简单,先获取到User类、类中声明的所有方法和成员变量。再分别遍历方法和成员变量并打印出来。上面有详细注释就不一一介绍了,调用getUserClass()可以看到输出了User类的方法和成员变量信息:
三、反射获取运行时注解信息
了解了反射和注解的基本原理,获取注解的信息就比较简单了。
给User中的userName添加注解:
public class User {
@MyAnnotation
private String userName;
....
获取注解信息赋值给userName:
private void getAnnotation() {
Class c = User.class;
User user = null;
try {
user = (User) c.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
//判断该变量是否被MyAnnotation注解修饰
if (field.isAnnotationPresent(MyAnnotation.class)) {
//获取注解
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
try {
//如果变量声明为private时,为了确保可以访问可将Accessible置为true
field.setAccessible(true);
//将当前属性的值修改为注解中成员变量的值
field.set(user, annotation.name());
System.out.println(user.getUserName());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
上述代码获取到变量后先判断该变量是否被自定义的MyAnnotation修饰了,如果是则获取注解。再把注解的值赋给该变量。值得注意的是:
- field.setAccessible(true) :这个方法是为了确保可以访问、修改被private修饰的变量。
- field.set(user, annotation.name());修改操作,参数1是被修改的变量所在的类,也就是User类。
这样就可以看到userName已经是注解中的默认值了:
原理上butterknife也是通过@bindview注解,最终调用了findviewByid初始化控件。但实际上butterknife用的是apt编译时注解处理,并且如何自动生成代码这里就不作讨论了。