Annotation系列之运行时注解使用

运行时注解(RetentionPolicy.RUNTIME)会被JVM保留,因此我们在代码运行过程中我们可以对类中使用的运行时注解信息进行获取和处理,运行时注解一般和动态代理配合使用,当然我们也可以利用反射根据注解信息对注解的对象进行相关使用。我们比较熟悉的网络库Retrofit就是通过定义了一系列运行时注解,在程序运行期间根据相应Service接口生成动态代理类,执行方法时会将Service接口方法中声明的注解信息转化成一个HTTP Call并进行相应的请求、数据解析及回调处理。

本篇将着重于描述如何获取动态注解信息,我们将定义一个运行时注解,类似Butter Knife的功能(Butter Knife使用的是编译时注解,在下一篇文章中探讨),只要使用注解就可以省去findViewById相关重复性代码(注:代码仅用于示例,因为反射的性能问题等并不具有使用价值)。

1. 定义注解

1
2
3
4
5
6
7
8
//声明注解范围为成员变量
@Target(ElementType.FIELD)
//声明注解为运行时注解
@Retention(RetentionPolicy.RUNTIME)
public @interface FindViewById {
//定义一个int类型的元素
int value();
}

2. 编写注解处理工具类

在这里我们需要了解下AnnotatedElement接口,其在java.lang.reflect包中,代表当前运行在VM中的程序的一个注解元素,AnnotatedElement接口允许注解信息被反射读取。该接口的方法中返回的注解信息都是不可变和可序列化的。该接口方法返回的数据可以被调用者修改而不影响返回给其他调用者的数组信息。我们在反射中用到的 Class, Constructor, Field, Method, Parameter等都是实现了AnnotatedElement接口,因此都可以通过AnnotatedElement接口中定义的方法获取到这些元素上面的注解信息。这里列出常用几个常用方法:

<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。与接口中的其他方法不同,该方法将忽略继承的注解。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组,不会对其他调用者返回的数组产生任何影响。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//为了简单演示,我们的工具类只针对Activity进行处理
public class ViewInjector {

//为了简单演示,我们的工具类只针对Activity进行处理
public static void inject(final Activity target) {
//收集当前Activity及父类Activity中Field信息(防止遗漏父类中使用的注解)
Class targetClass = target.getClass();
List<Field> fieldList = new ArrayList<>();
while (targetClass != null) {
Field[] fields = targetClass.getDeclaredFields();
Collections.addAll(fieldList, fields);
targetClass = targetClass.getSuperclass();
}
for (Field field : fieldList) {
//获取@FindViewById注解的成员变量
FindViewById annotation = field.getAnnotation(FindViewById.class);
if (annotation != null) {
try {
field.setAccessible(true);
//通过findViewById找到对应View并为View变量反射赋值
field.set(target, target.findViewById(annotation.value()));
} catch (Exception e) {
throw new RuntimeException("can't find view by id :" + annotation.value());
}
}
}
}
}

3. 使用注解及工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {

@FindViewById(R.id.btn_sign_in)
private Button mSubmitBtn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用工具类解析注解信息并赋值
ViewInjector.inject(this);
//mSubmitBtn将会指向@FindViewById中id对应的View
Toast.makeText(this,"mSubmitBtn:"+mSubmitBtn,Toast.LENGTH_SHORT).show();
}
}

类似我们可以通过反射获取到方法中的注解信息,就像刚开始提到的,我们只需要学会注解信息的获取方式即可,Demo源码详见关于三种注解类型的示例Demo

文章目录
  1. 1. 定义注解
  2. 2. 编写注解处理工具类
  3. 3. 使用注解及工具类
|