关于注解,反射和动态代理,可能在正常的开发中不一定用的频率很高,但是有时候可能一些特殊的需求,或者想开发一个框架,这些技术可能是必备技能,下面通过一个小案例,来加深对这些技术的理解。
需求很简单,ButterKnife大家都是知道,下面这个小案例就模仿ButterKnife来做个注解,然后通过注解来处理相关的行为,话不多说,开撸。先看下主界面的代码:
```java
@Onclick({R.id.btn1,R.id.btn2})
private void onClickBtn(View view){
int id = view.getId();
switch (id){
case R.id.btn1:
Log.e("anre","btn1");
break;
case R.id.btn2:
Log.e("anre","btn2");
break;
default:
Log.e("anre","btn3");
break;
}
}
@OnLongclick({R.id.btn1,R.id.btn2})
private boolean onLongClickBtn(View view){
int id = view.getId();
switch (id){
case R.id.btn1:
Log.e("anre","btn1-Long");
break;
case R.id.btn2:
Log.e("anre","btn2-Long");
break;
default:
Log.e("anre","btn3-Long");
break;
}
return true;
}
```
可以看到分别在Activity中定义了两个方法,一个点击,一个长按,然后在这2个方法中分别加了2个注解,@Onclick和@OnLongclick,下面来看下这2个注解的定义:
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(parameterType = View.OnClickListener.class,funName = "setOnClickListener")
public @interface Onclick {
int[] value();
}
```
<center>@Onclick注解</center>
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(parameterType = View.OnLongClickListener.class,funName = "setOnLongClickListener")
public @interface OnLongclick {
int[] value();
}
```
<center>@OnLongclick注解</center>
上面2段就是这2个注解,其他都比较好理解,都是注解方法的,都是运行时保留,其中还有个元注解@EventType,这个暂且不说,留到后面点在说,因为会实现2个版本,第一个版本这个用不到,第二个版本在说这个元注解。
注解定义好了,然后我们定义解析注解的方法,看如下代码:
```java
public static void inject(final Activity activity){
//获取当前类中的方法
Method[] methods = activity.getClass().getDeclaredMethods();
for (final Method method : methods) {
//获取当前方法的注解
Annotation[] annotations = method.getAnnotations();
//如果是private注解,获取权限
method.setAccessible(true);
for (Annotation annotation : annotations) {
//如果是Onclick注解
if(annotation instanceof Onclick){
//获取当前Onclick注解上的value方法值,此处就是控件的id
int[] ids = ((Onclick)annotation).value();
for (int id : ids) {
final View view = activity.findViewById(id);
//遍历每个控件,设置点击方法
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//反射调用该方法,一个参数是该方法属于的实例,第二个参数是该方法的参数
method.invoke(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}else if (annotation instanceof OnLongclick){
//长按事件流程同上
int[] ids = ((OnLongclick)annotation).value();
for (int id : ids) {
final View view = activity.findViewById(id);
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
try {
method.invoke(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
});
}
}
}
}
}
```
上面注释里面把主要的地方都写了,应该都比较好理解,但是有没有发觉,上面方面还要判断不同的注解,然后其实在里面执行的方法都差不多,有没有更简洁的方法?记得刚才注解的2个类中那个元注解@EventType吗,来看下@EventType的代码:
```java
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
Class parameterType();
String funName();
}
```
这是个元注解,就是给注解用的注解,所以我们前面把这个注解写在了两个注解上,这个注解里面定义了两个方法,一个是个Class,其实这个是个接口,后面要给动态代理来用,表示要代码的方法属于哪个接口,另一个funName代表要代理的那个方法的名字,好了,下面看下结合动态代理的第二版代码:
```java
public static void inject2(final Activity activity) {
Method[] methods = activity.getClass().getDeclaredMethods();
for (final Method method : methods) {
Annotation[] annotations = method.getAnnotations();
method.setAccessible(true);
for (Annotation annotation : annotations) {
//获取当前注解的类型
Class<? extends Annotation> annotationType = annotation.annotationType();
//这句话判断,括号中的注解是否在annotationType上
if (annotationType.isAnnotationPresent(EventType.class)) {
//获取括号中的注解,这里也就是元注解
EventType eventType = annotationType.getAnnotation(EventType.class);
//下面获取我们定义的两个方法的值
Class parameterType = eventType.parameterType();
String funName = eventType.funName();
MyInvocation invocationHandler = new MyInvocation(activity,method);
//创建动态代理实例,下面方法会通过返回设置,把该动态代理传过去
Object object = Proxy.newProxyInstance(parameterType.getClassLoader(), new Class[]{parameterType}, invocationHandler);
try {
//反射获取当前注解上的value方法中的值,如果不通过反射,还需要转型,比如
((Onclick) annotation).value(),这样还要做if判断,就显得麻烦点了,
另外这里annotationType就是annotation.getClass()
Method idMethod = annotationType.getDeclaredMethod("value");
int[] ids = (int[]) idMethod.invoke(annotation);
for (int id : ids) {
View view = activity.findViewById(id);
//反射获取当前view的funName方法,以我们这里onClick接口为例,funName的值是
setOnClickListener,parameterType是View.OnClickListener.class,代表
获取view的setOnClickListener方法,该方法参数类型是View.OnClickListener.class
Method methodexe = view.getClass().getMethod(funName, parameterType);
//反射调用该方法,这里表示执行view中的setOnClickListener方法,object作为方法参数
methodexe.invoke(view, object);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
static class MyInvocation<T> implements InvocationHandler {
//target表示执行方法的实例
private T target;
//要执行的方法
private Method method;
public MyInvocation(T target, Method method) {
this.target = target;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method2, Object[] args) throws Throwable {
//执行target实例中的method方法,注意这里method是指从构造函数中传进来的方法,不是上面method2这个
return this.method.invoke(target,args);
}
}
```
上面第二版结合了注解,反射和动态代理,虽然比较简单,但是对于理解还是比较有益处的,许多三方源码也就是在这些简单的基础上再扩充的。
注解,反射,动态代理小案例