动态代理-事件注入(二)

动态代理-事件注入(一) 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

来源url
栏目