美文网首页
《撸代码学习 IOC注入技术2》—— 事件注入

《撸代码学习 IOC注入技术2》—— 事件注入

作者: 倔脾气的皮皮虾啊 | 来源:发表于2020-02-29 21:16 被阅读0次

    不诗意的女程序媛不是好厨师~
    转载请注明出处,From李诗雨---https://blog.csdn.net/cjm2484836553/article/details/104581855

    源代码下载地址:https://github.com/junmei520/iocStudy
    在这里插入图片描述

    在上一篇 的文章 《撸代码 学习 IOC注入技术1 》—— 布局注入 与 控件注入中,我们已经自己通过敲代码,一步一步实现了,运行时注入的---布局注入和控件注入。那么今天,我将来继续敲代码,来一步一步实现,事件的注入。

    先来看一下我要达到的效果:


    在这里插入图片描述

    即:我想通过这两句代码,就实现点击事件。

    根据上一篇中我们讲的 布局注入 和 控件注入 的经验,大家对于实现我们今天的 事件注入 有没有什么想法和思路呢?

    对!我们还是要自己造个女朋友InjectUtils,然后在BaseActivity中就进行注入。

    那接下来呢?接下来继续要怎么做?你会不会是这样想的:


    在这里插入图片描述

    你是不是想:"那还不简单吗?和之前的类似啊,先自定义一个注解OnClick,然后具体实现InjetUtils中的injectEvent()方法呀!"

    那我有要问:"那你打算具体怎么实现injectEvent()呢?"

    你是不是还会像这样回答:“当然主要还是通过反射啦,①先获取activity的所有方法;②再获取方法上的 OnClick 注解,进而得到注解后面的id ,然后得到button;③最后反射执行 btn1.setOnClickListener(new View.OnClickListener() {...}巴拉巴拉巴拉...”

    在这里插入图片描述

    emmm... 我想说,你这么想其实也没有什么大问题,就是有点,emmm...,有点太low啦。因为如果你这样做的话,那就是把代码写死了呀~~~

    比如说,如果我还想加个长按事件呢,像这样:


    在这里插入图片描述

    你可能会说,那我就改injectEvent()代码呀!

    emmm...我忍!那如果我再继续增加几个事件呢?你还打算继续改injectEvent()的内部代码吗?还打算增加许多的if/else或很多的谜之缩进吗???你自己体会一下~~~

    显然这样把代码写死是不可行的,那我们怎么样才能使得我们自己的代码变得灵活呢?

    那我们就必须寻找不同事件的共同点了,然后把相同点抽取出来。

    让我们再用新的眼光来审视一下短按和长按事件:


    在这里插入图片描述

    我们可以总结出,所有的事件都具有三要素:

    • 1.事件源
    • 2.事件
    • 3.事件的处理
    • 最后还要进行订阅(订阅关系)

    既然知道了这一点,那我们再自定义注解OnClick的时候就可以把这三要素也加进去了。

    下面我们就来正式的讲讲正确的思路了:

    首先我们先自定义一个注解BaseEvent,它可以用来接受事件的三要素信息,并且将来会把它用在OnClick注解的身上:

    @Target(ElementType.ANNOTATION_TYPE) //该注解是用在自定义注解上的
    @Retention(RetentionPolicy.RUNTIME)  //可以保留到程序运行时
    public @interface BaseEvent {
        Class<?> enventType();  //事件 ---> 即相当于 new View.OnClickListener()
    
        String setterMethod(); //订阅关系 ---> 即相当于 setOnClickListener()
    
        String callbackMethod(); // 事件回调方法 ---> 即相当于 onClick()
    
    }
    

    然后我们再来定义OnClick注解,它的头上使用了BaseEvent注解,并传入三要素。

    @Target(ElementType.METHOD) //该注解是用在方法上的
    @Retention(RetentionPolicy.RUNTIME) //该注解可以保持到程序运行时
    @BaseEvent(enventType = View.OnClickListener.class,
            setterMethod = "setOnClickListener",
            callbackMethod = "onClick")
    public @interface OnClick {
        int[] value() default -1; //由于可能是多个id,所以此处要用数组来接收
    }
    

    使用的时候就这样:

    @OnClick({R.id.button1, R.id.button2})
    public void click(View view) {
        //...具体操作...
    }
    

    如果要再加入其他的事件,也很好办,比如我要再加一个长按事件,那我就只要多增加一个OnLongClick的注解就可以了,它的地方都不用做任何的修改。其实,这就是我们所说的 注解的多态。

    //增加一个长按事件
    @OnLongClick({R.id.button1, R.id.button2})
    public boolean longClick(View view) {
        //...具体操作...
        return false;
    }
    
    //只要多增加一个自定义的注解就可以了,传入具体的事件三要素。
    @Target(ElementType.METHOD) //该注解是用在方法上的
    @Retention(RetentionPolicy.RUNTIME) //该注解可以保持到程序运行时
    @BaseEvent(enventType = View.OnLongClickListener.class,
            setterMethod = "setOnLongClickListener",
            callbackMethod = "onLongClick")
    public @interface OnLongClick {
        int[] value() default -1; //由于可能是多个id,所以此处要用数组来接收
    }
    

    一些小说明:

    1.由于我们在使用OnClick注解时传入了控件的id, 所以在自定义BaseEvent注解时,事件源就没有必要再传进去了。

    2.由于在使用OnClick注解时,可能传入的是多个控件的id, 所以自定义OnClick注解时,要用int[]数组来接收。

    好了,完成了这些铺垫工作,下面就让我们来集中精力实现InJectUtils中的injectEvent()方法吧~

    首先,我们来思考一下,我们需要做哪些事情呢?


    在这里插入图片描述

    我们来分析一下,首先由于我们的OnClick注解是用在方法上的,所以

    • 第一步,就是要获取activity上的所有方法。(目的是为了可以找到使用了OnClick注解的click()方法)

    • 第二步,我们要对所有的注解进行遍历,获取每一个方法上的所有注解。(通过这一步我们可以获取click()上的OnClick注解)

    • 第三步,我们要拿到注解类型对应的Class,通过Class去找,看看有没有BaseEvent,如果有则说明这个方法就是事件方法。(通过这一步我们可以得到OnClick对应注解类型的Class,从而进一步找到BaseEvent,并且可以确定click()就是事件方法)

    • 第四步,从BaseEvent中拿到事件的三要素。

      (①通过enventType()-->得到事件:即相当于 new View.OnClickListener();

      ②通过setterMethod()-->得到订阅关系:即相当于 setOnClickListener();

      ③通过callbackMethod()-->得到事件回调方法:即相当于 onClick())

    • 第五步,接下来就是反射执行

      btn1.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
      
           }
       });
      

      了。

    我们来看一下代码实现:

     private static void injectEvent(Object context) {
            Class<?> clazz = context.getClass();
            //1.获取该activity上的所有方法
            Method[] methods = clazz.getDeclaredMethods();
    
            //2.循环遍历方法,拿到每一个方法上的所有注解
            for (Method method : methods) {
                Annotation[] annotations = method.getAnnotations();
                //3.循环遍历注解,拿到注解类型对应的Class,通过class去找,看看有没有BaseEvent
                for (Annotation annotation : annotations) {
                    //拿到注解类型对应的Class
                    Class<?> annotationClass = annotation.annotationType();
                    //通过Class去找,看看有没有BaseEvent
                    BaseEvent baseEvent = annotationClass.getAnnotation(BaseEvent.class);
    
                    //如果没有BaseEvent,则表示当前方法不是一个事件处理的方法
                    if (baseEvent == null) {
                        continue;
                    }
    
                    //4.如果有BaseEvent,则表示是事件处理的方法,那我们就去拿事件的三要素
                    //拿到三要素
                    Class<?> eventType = baseEvent.enventType();
                    String setterMethodStr = baseEvent.setterMethod();
                    String callbackMethod = baseEvent.callbackMethod();
    
                    //5.接下来我们要反射执行
    //                   btn1.setOnClickListener(new View.OnClickListener() {
    //                        @Override
    //                        public void onClick(View view) {
    //
    //                        }
    //                    });
                }
            }
        }
    

    关于第五步,反射执行btn.setOnClickListener(new View.OnClickListener(){ public void onClick()}),我们要单独提出来分析一下。

    在这里插入图片描述
    • 1.首先我们需要拿到事件源,(即对应的view控件,此处即指btn按钮)。

      那我们要怎么做呢?对,首先我们要拿到控件id.

      ✪那控件id要怎么样才能拿到呢?我们是不是在上面已经拿到了注解类型对应的Class,那我们就可以根据方法名,通过反射拿到value()对应的method;然后我们再反射执行valueMethod,就可以拿到id了。

      ✪有了id就好办了,再反射执行findViewById,就可以拿到对应的view控件了。

    • 2.要拿到事件(即此处的[new View.OnClickListener()]),这个我们通过上面的BaseEvent已经得到了,即eventTpye。

    • 3.我们还要拿到订阅关系(即setOnClickListener()),这个也好办,通过上面的BaseEvent我们不是已经拿到了方法名的字符串了吗,那再通过反射拿到对应的setterMethod就可以啦。

    • 4.我们,是不是还差一个事件的处理(onClick())。但是,我们写的这个框架,并不知道将来按钮要具体执行哪些操作呀?对于未知的东西我们该怎么处理呢?对啦!用动态代理。

    在这里插入图片描述

    那下面我们就来具体看看这个动态代理该怎么写吧~

    首先我们要自定义一个MyInvocationHandler类,因为要代理的真实对象是activity中的click()方法,所以,我们需要两个属性,并在构造函数中直接传入,我们还知道最终会调用这个类里的invoke()方法,而这里要执行的应该是真实对象要执行的操作,所以此处直接调用activityMethod.invoke(activity,objects);

    //代理的是 new View.OnClickLisener()对象
    //并且最终执行的是activity的click()方法
    public class MyInvocationHandler implements InvocationHandler {
        private Object activity;
        private Method activityMethod;
    
        public MyInvocationHandler(Object activity, Method activityMethod) {
            this.activity = activity;
            this.activityMethod = activityMethod;
        }
    
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            return activityMethod.invoke(activity, objects);
        }
    }
    

    好了,那我们就把第五步的步骤也补充上去吧:

    private static void injectEvent(Object context) {
            Class<?> clazz = context.getClass();
            //1.获取该activity上的所有方法
            Method[] methods = clazz.getDeclaredMethods();
    
            //2.循环遍历方法,拿到每一个方法上的所有注解
            for (Method method : methods) {
                Annotation[] annotations = method.getAnnotations();
                //3.循环遍历注解,拿到注解类型对应的Class,通过class去找,看看有没有BaseEvent
                for (Annotation annotation : annotations) {
                    //拿到注解类型对应的Class
                    Class<?> annotationClass = annotation.annotationType();
                    //通过Class去找,看看有没有BaseEvent
                    BaseEvent baseEvent = annotationClass.getAnnotation(BaseEvent.class);
    
                    //如果没有BaseEvent,则表示当前方法不是一个事件处理的方法
                    if (baseEvent == null) {
                        continue;
                    }
    
                    //4.如果有BaseEvent,则表示是事件处理的方法,那我们就去拿事件的三要素
                    //拿到三要素
                    Class<?> eventType = baseEvent.enventType();
                    String setterMethodStr = baseEvent.setterMethod();
                    String callbackMethod = baseEvent.callbackMethod();
    
                    //5.接下来我们要反射执行
    //                   btn1.setOnClickListener(new View.OnClickListener() {
    //                        @Override
    //                        public void onClick(View view) {
    //
    //                        }
    //                    });
    
                    //5.1首先我们需要拿到事件源,(即对应的view控件,此处即指btn按钮)
                    try {
                        //先获取注解中的value方法,即 OnClick中的value()
                        Method valueMethod = annotationClass.getDeclaredMethod("value");
                        //再反射执行 OnClick注解的 value()方法,得到id
                        int[] ids = (int[]) valueMethod.invoke(annotation);
                        for (int id : ids) {
                            //反射执行context.findViewById(id)得到对应的view
                            Method findViewByIdMethod = clazz.getMethod("findViewById", int.class);
                            View view = (View) findViewByIdMethod.invoke(context, id);
                            if (view == null) {
                                continue;
                            }
    
                            //5.2要拿到事件(即[new View.OnClickListener()]),这个我们通过上面的BaseEvent已经得到了,即eventTpye。
                            //5.3拿到订阅关系(即setOnClickListener()),即根据setterMethodStr得到setterMethod
    
                            //5.4动态代理了  //activity==context    click===method
                            MyInvocationHandler myInvocationHandler = new MyInvocationHandler(context, method);
                            Object proxy = Proxy.newProxyInstance(eventType.getClassLoader(),
                                    new Class[]{eventType}, myInvocationHandler);
    
                            //  让proxy执行的click()
                            //参数1  setOnClickListener()的名称
                            //参数2  new View.OnClickListener()对象
                            Method setterMethod = view.getClass().getMethod(setterMethodStr, eventType);
                            // 反射执行  view.setOnClickListener(new View.OnClickListener())
                            setterMethod.invoke(view, proxy);
                            //这时候,点击按钮时就会去执行代理类中的invoke方法()了
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                }
            }
        }
    

    好了,到这里我们所有的事件注入工作就都完成了,赶快在测试一下吧:

    //在MainActivity中进行使用测试
    @OnClick({R.id.button1, R.id.button2})
    public void click(View view) {
        switch (view.getId()) {
            case R.id.button1:
                Toast.makeText(this, "短按下了", Toast.LENGTH_SHORT).show();
                break;
            case R.id.button2:
                Toast.makeText(this, "短按下了222", Toast.LENGTH_SHORT).show();
                break;
        }
    }
    
    //增加一个长按事件,注意这里的方法返回类型要和系统中的保持一致
    @OnLongClick({R.id.button1, R.id.button2})
    public boolean longClick(View view) {
        switch (view.getId()) {
            case R.id.button1:
                Toast.makeText(this, "好好学习", Toast.LENGTH_SHORT).show();
                break;
            case R.id.button2:
                Toast.makeText(this, "天天向上", Toast.LENGTH_SHORT).show();
                break;
        }
        return false;
    }
    

    运行结果:

    在这里插入图片描述 在这里插入图片描述
    源代码下载地址:https://github.com/junmei520/iocStudy

    积累点滴,做好自己~

    相关文章

      网友评论

          本文标题:《撸代码学习 IOC注入技术2》—— 事件注入

          本文链接:https://www.haomeiwen.com/subject/tlrbhhtx.html