动态代理-事件注入(二)
动态代理-事件注入(一) https://blog.csdn.net/wumeixinjiazu/article/details/122499731
反射方法代码总结:
getMethod 可以获取父类的方法 getDeclaredMethod 不行,都不能获取父类的私有方法getMethod getDeclaredMethod 都可以获取自己类的方法。但是对于私有方法,必须使用getDeclaredMethod,然后setAccessiblegetField 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
类
需要把回调返回,因为长按事件需要返回值
@Overridepublic 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 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.OnClickListenerObject proxy =Proxy.newProxyInstance(content.getClass().getClassLoader(), new Class[]{listenerType}, new OnClickInvocationHandler(content, method));//6.1 获取设置监听方法 setOnClickListenerMethod 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