动态代理-事件注入(一) https://blog.csdn.net/wumeixinjiazu/article/details/122499731
反射方法代码总结:
getMethod 可以获取父类的方法 getDeclaredMethod 不行,都不能获取父类的私有方法
getMethod getDeclaredMethod 都可以获取自己类的方法。但是对于私有方法,必须使用getDeclaredMethod,然后setAccessible
getField getDeclaredField 都可以获取父类的变量,都不能获取父类的私有方法
getField getDeclaredField 都可以获取自己类的变量。但是对于私有方法,必须使用getDeclaredField,然后setAccessible
先看看上一节留下的问题:
1.那就是如果你还要注册长按事件,viewpager的滑动事件,那是不是还得继续往文件写代码,违背了类的单一原则,也不好扩展,
2.你如何区分哪个控件需要长按,哪个不需要?按照下面的写法,所有的控件都会被自动注册长按事件。
先解决第二个问题:
例如,我们现在需要给另外一个控件注册长按事件
第一步:同样,创建一个长按注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface onLongClick {
//接收控件的ID
int[] value() default -1;
}
第二步:修改InjectUtil里的逻辑
在init类中,多加一个OnLongClick注解
OnLongClick longClick = method.getAnnotation(OnLongClick.class);
if (longClick != null) {
int[] value = longClick.value();
try {
//3.通过反射获取findViewById方法
Method findViewById = content.getClass().getMethod("findViewById", int.class);
for (int i : value) {
//4.获取控件
View view = (View) findViewById.invoke(content, i);
//5.通过代理获取View.OnLongClickListener
View.OnLongClickListener listener = (View.OnLongClickListener) Proxy.newProxyInstance(content.getClass().getClassLoader(),
new Class[]{View.OnLongClickListener.class}, new OnClickInvocationHandler(content, method));
//6.设置监听
view.setOnLongClickListener(listener);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
OnClickInvocationHandler
类
需要把回调返回,因为长按事件需要返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (content != null) {
//这里的回调是 setOnClickListener的回调
//method 也就是start方法
return this.method.invoke(content,args);
}
return null;
}
MainActivity
同样,方法需要设置返回值,因为长按事件需要返回值
@OnLongClick(R.id.longbtn)
public boolean startLongClick(View view) {
Toast.makeText(this,"我被长按了",Toast.LENGTH_SHORT).show();
return true;
}
至此,第二个问题解决了。但还是又暴露出了第一个问题,违背了类的单一原则,也不好扩展,每次扩展难道都要在类里面添加方法。接下来我们看看如何解决?
假如我们动态代理的时候,可以找到事件注册规律,那是不是就可以统一注册,让我们一起来看看怎么做
第一步:找找规律
看看下面的点击事件和长按事件有什么规律嘛?
View view = new View(this);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
咋一看,基本没啥规律,设置方法不一样,传的参数不一样,返回的回调也不一样。
其实,规律还是有的,就是他们注册的流程都一样
首先,都需要设置方法 例如:setOnClickListener,setOnLongClickListener
其次,都需要传参数 例如:View.OnClickListener,View.OnLongClickListener
最后,都有回调 例如: public void onClick(View v)
那我们看看,如何利用上面这些规律来统一注册的流程。
第二步:增加一个父类注解,接受子类注解的参数
BaseEvent
类,所有事件注解的父类
因为注解没有继承,但是却可以在注解上添加注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface BaseEvent {
/**
* 设置事件监听的方法 setOnClickListener
*
* @return
*/
String listenerSetter();
/**
* 事件监听的类型 OnClickListener 事件类型
*
* @return
*/
Class<?> listenerType();
/**
* 事件被触发之后,执行的回调方法的名称 3 回调方法
*
* @return
*/
String callbackMethod();
}
看看对应的OnClick
类要怎么修改
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@BaseEvent(listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,callbackMethod = "onClick")
public @interface OnClick {
//接收控件的ID
int[] value() default -1;
}
InjectUtil
类修改
public static void init(Object content) {
Method[] methods = content.getClass().getMethods();
for (Method method : methods) {
//1.获取方法中所有的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//2.找到注解的注解是BaseEvent的
Class<? extends Annotation> annotationType = annotation.annotationType();
BaseEvent baseEvent = annotationType.getAnnotation(BaseEvent.class);
if (baseEvent != null) {
//3.1获取需要设置的方法
String listenerSetter = baseEvent.listenerSetter();
//3.2获取需要传的参数
Class<?> listenerType = baseEvent.listenerType();
//3.3获取需要返回的回调
String callbackMethod = baseEvent.callbackMethod();
try {
//4.1 获取注解中value方法
Method value = annotationType.getDeclaredMethod("value");
//4.1 获取需要注册的控件
int[] values = (int[]) value.invoke(annotation);
try {
//3.通过反射获取findViewById方法
Method findViewById = content.getClass().getMethod("findViewById", int.class);
for (int i : values) {
//4.获取控件
View view = (View) findViewById.invoke(content, i);
//5.通过代理获取View.OnClickListener
Object proxy =Proxy.newProxyInstance(content.getClass().getClassLoader(), new Class[]{listenerType}, new OnClickInvocationHandler(content, method));
//6.1 获取设置监听方法 setOnClickListener
Method method1 = view.getClass().getMethod(listenerSetter,listenerType);
//6.2 设置监听
method1.invoke(view,proxy);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
到此为止,第一个问题也解决了。假如我们现在需要设置长按事件,只要创建一个长按注解类,并且添加上BaseEvent的注解,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@BaseEvent(listenerSetter = "setOnLongClickListener",listenerType = View.OnLongClickListener.class,callbackMethod = "onLongClick")
public @interface OnLongClick {
//接收控件的ID
int[] value() default -1;
}
总结一下:代码不难,重要的是思路。
核心:反射+动态代理
1.找到方法上的注解上需要注册的事件
2.通过反射+动态代理的方式设置事件
代码地址:IOCDemo: 动态代理ioc注解https://gitee.com/small_insects/IOCDemo