《擼代碼學(xué)習(xí) IOC注入技術(shù)2》—— 事件注入

不詩(shī)意的女程序媛不是好廚師~
轉(zhuǎn)載請(qǐng)注明出處,F(xiàn)rom李詩(shī)雨---https://blog.csdn.net/cjm2484836553/article/details/104581855

源代碼下載地址:https://github.com/junmei520/iocStudy
在這里插入圖片描述

在上一篇 的文章 《擼代碼 學(xué)習(xí) IOC注入技術(shù)1 》—— 布局注入 與 控件注入中,我們已經(jīng)自己通過(guò)敲代碼,一步一步實(shí)現(xiàn)了,運(yùn)行時(shí)注入的---布局注入和控件注入。那么今天,我將來(lái)繼續(xù)敲代碼,來(lái)一步一步實(shí)現(xiàn),事件的注入。

先來(lái)看一下我要達(dá)到的效果:


在這里插入圖片描述

即:我想通過(guò)這兩句代碼,就實(shí)現(xiàn)點(diǎn)擊事件。

根據(jù)上一篇中我們講的 布局注入 和 控件注入 的經(jīng)驗(yàn),大家對(duì)于實(shí)現(xiàn)我們今天的 事件注入 有沒(méi)有什么想法和思路呢?

對(duì)!我們還是要自己造個(gè)女朋友InjectUtils,然后在BaseActivity中就進(jìn)行注入。

那接下來(lái)呢?接下來(lái)繼續(xù)要怎么做?你會(huì)不會(huì)是這樣想的:


在這里插入圖片描述

你是不是想:"那還不簡(jiǎn)單嗎?和之前的類(lèi)似啊,先自定義一個(gè)注解OnClick,然后具體實(shí)現(xiàn)InjetUtils中的injectEvent()方法呀!"

那我有要問(wèn):"那你打算具體怎么實(shí)現(xiàn)injectEvent()呢?"

你是不是還會(huì)像這樣回答:“當(dāng)然主要還是通過(guò)反射啦,①先獲取activity的所有方法;②再獲取方法上的 OnClick 注解,進(jìn)而得到注解后面的id ,然后得到button;③最后反射執(zhí)行 btn1.setOnClickListener(new View.OnClickListener() {...}巴拉巴拉巴拉...”

在這里插入圖片描述

emmm... 我想說(shuō),你這么想其實(shí)也沒(méi)有什么大問(wèn)題,就是有點(diǎn),emmm...,有點(diǎn)太low啦。因?yàn)槿绻氵@樣做的話,那就是把代碼寫(xiě)死了呀~~~

比如說(shuō),如果我還想加個(gè)長(zhǎng)按事件呢,像這樣:


在這里插入圖片描述

你可能會(huì)說(shuō),那我就改injectEvent()代碼呀!

emmm...我忍!那如果我再繼續(xù)增加幾個(gè)事件呢?你還打算繼續(xù)改injectEvent()的內(nèi)部代碼嗎?還打算增加許多的if/else或很多的謎之縮進(jìn)嗎???你自己體會(huì)一下~~~

顯然這樣把代碼寫(xiě)死是不可行的,那我們?cè)趺礃硬拍苁沟梦覀冏约旱拇a變得靈活呢?

那我們就必須尋找不同事件的共同點(diǎn)了,然后把相同點(diǎn)抽取出來(lái)。

讓我們?cè)儆眯碌难酃鈦?lái)審視一下短按和長(zhǎng)按事件:


在這里插入圖片描述

我們可以總結(jié)出,所有的事件都具有三要素:

  • 1.事件源
  • 2.事件
  • 3.事件的處理
  • 最后還要進(jìn)行訂閱(訂閱關(guān)系)

既然知道了這一點(diǎn),那我們?cè)僮远x注解OnClick的時(shí)候就可以把這三要素也加進(jìn)去了。

下面我們就來(lái)正式的講講正確的思路了:

首先我們先自定義一個(gè)注解BaseEvent,它可以用來(lái)接受事件的三要素信息,并且將來(lái)會(huì)把它用在OnClick注解的身上:

@Target(ElementType.ANNOTATION_TYPE) //該注解是用在自定義注解上的
@Retention(RetentionPolicy.RUNTIME)  //可以保留到程序運(yùn)行時(shí)
public @interface BaseEvent {
    Class<?> enventType();  //事件 ---> 即相當(dāng)于 new View.OnClickListener()

    String setterMethod(); //訂閱關(guān)系 ---> 即相當(dāng)于 setOnClickListener()

    String callbackMethod(); // 事件回調(diào)方法 ---> 即相當(dāng)于 onClick()

}

然后我們?cè)賮?lái)定義OnClick注解,它的頭上使用了BaseEvent注解,并傳入三要素。

@Target(ElementType.METHOD) //該注解是用在方法上的
@Retention(RetentionPolicy.RUNTIME) //該注解可以保持到程序運(yùn)行時(shí)
@BaseEvent(enventType = View.OnClickListener.class,
        setterMethod = "setOnClickListener",
        callbackMethod = "onClick")
public @interface OnClick {
    int[] value() default -1; //由于可能是多個(gè)id,所以此處要用數(shù)組來(lái)接收
}

使用的時(shí)候就這樣:

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

如果要再加入其他的事件,也很好辦,比如我要再加一個(gè)長(zhǎng)按事件,那我就只要多增加一個(gè)OnLongClick的注解就可以了,它的地方都不用做任何的修改。其實(shí),這就是我們所說(shuō)的 注解的多態(tài)。

//增加一個(gè)長(zhǎng)按事件
@OnLongClick({R.id.button1, R.id.button2})
public boolean longClick(View view) {
    //...具體操作...
    return false;
}
//只要多增加一個(gè)自定義的注解就可以了,傳入具體的事件三要素。
@Target(ElementType.METHOD) //該注解是用在方法上的
@Retention(RetentionPolicy.RUNTIME) //該注解可以保持到程序運(yùn)行時(shí)
@BaseEvent(enventType = View.OnLongClickListener.class,
        setterMethod = "setOnLongClickListener",
        callbackMethod = "onLongClick")
public @interface OnLongClick {
    int[] value() default -1; //由于可能是多個(gè)id,所以此處要用數(shù)組來(lái)接收
}

一些小說(shuō)明:

1.由于我們?cè)谑褂肙nClick注解時(shí)傳入了控件的id, 所以在自定義BaseEvent注解時(shí),事件源就沒(méi)有必要再傳進(jìn)去了。

2.由于在使用OnClick注解時(shí),可能傳入的是多個(gè)控件的id, 所以自定義OnClick注解時(shí),要用int[]數(shù)組來(lái)接收。

好了,完成了這些鋪墊工作,下面就讓我們來(lái)集中精力實(shí)現(xiàn)InJectUtils中的injectEvent()方法吧~

首先,我們來(lái)思考一下,我們需要做哪些事情呢?


在這里插入圖片描述

我們來(lái)分析一下,首先由于我們的OnClick注解是用在方法上的,所以

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

  • 第二步,我們要對(duì)所有的注解進(jìn)行遍歷,獲取每一個(gè)方法上的所有注解。(通過(guò)這一步我們可以獲取click()上的OnClick注解)

  • 第三步,我們要拿到注解類(lèi)型對(duì)應(yīng)的Class,通過(guò)Class去找,看看有沒(méi)有BaseEvent,如果有則說(shuō)明這個(gè)方法就是事件方法。(通過(guò)這一步我們可以得到OnClick對(duì)應(yīng)注解類(lèi)型的Class,從而進(jìn)一步找到BaseEvent,并且可以確定click()就是事件方法)

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

    (①通過(guò)enventType()-->得到事件:即相當(dāng)于 new View.OnClickListener();

    ②通過(guò)setterMethod()-->得到訂閱關(guān)系:即相當(dāng)于 setOnClickListener();

    ③通過(guò)callbackMethod()-->得到事件回調(diào)方法:即相當(dāng)于 onClick())

  • 第五步,接下來(lái)就是反射執(zhí)行

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

    了。

我們來(lái)看一下代碼實(shí)現(xiàn):

 private static void injectEvent(Object context) {
        Class<?> clazz = context.getClass();
        //1.獲取該activity上的所有方法
        Method[] methods = clazz.getDeclaredMethods();

        //2.循環(huán)遍歷方法,拿到每一個(gè)方法上的所有注解
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            //3.循環(huán)遍歷注解,拿到注解類(lèi)型對(duì)應(yīng)的Class,通過(guò)class去找,看看有沒(méi)有BaseEvent
            for (Annotation annotation : annotations) {
                //拿到注解類(lèi)型對(duì)應(yīng)的Class
                Class<?> annotationClass = annotation.annotationType();
                //通過(guò)Class去找,看看有沒(méi)有BaseEvent
                BaseEvent baseEvent = annotationClass.getAnnotation(BaseEvent.class);

                //如果沒(méi)有BaseEvent,則表示當(dāng)前方法不是一個(gè)事件處理的方法
                if (baseEvent == null) {
                    continue;
                }

                //4.如果有BaseEvent,則表示是事件處理的方法,那我們就去拿事件的三要素
                //拿到三要素
                Class<?> eventType = baseEvent.enventType();
                String setterMethodStr = baseEvent.setterMethod();
                String callbackMethod = baseEvent.callbackMethod();

                //5.接下來(lái)我們要反射執(zhí)行
//                   btn1.setOnClickListener(new View.OnClickListener() {
//                        @Override
//                        public void onClick(View view) {
//
//                        }
//                    });
            }
        }
    }

關(guān)于第五步,反射執(zhí)行btn.setOnClickListener(new View.OnClickListener(){ public void onClick()}),我們要單獨(dú)提出來(lái)分析一下。

在這里插入圖片描述
  • 1.首先我們需要拿到事件源,(即對(duì)應(yīng)的view控件,此處即指btn按鈕)。

    那我們要怎么做呢?對(duì),首先我們要拿到控件id.

    ?那控件id要怎么樣才能拿到呢?我們是不是在上面已經(jīng)拿到了注解類(lèi)型對(duì)應(yīng)的Class,那我們就可以根據(jù)方法名,通過(guò)反射拿到value()對(duì)應(yīng)的method;然后我們?cè)俜瓷鋱?zhí)行valueMethod,就可以拿到id了。

    ?有了id就好辦了,再反射執(zhí)行findViewById,就可以拿到對(duì)應(yīng)的view控件了。

  • 2.要拿到事件(即此處的[new View.OnClickListener()]),這個(gè)我們通過(guò)上面的BaseEvent已經(jīng)得到了,即eventTpye。

  • 3.我們還要拿到訂閱關(guān)系(即setOnClickListener()),這個(gè)也好辦,通過(guò)上面的BaseEvent我們不是已經(jīng)拿到了方法名的字符串了嗎,那再通過(guò)反射拿到對(duì)應(yīng)的setterMethod就可以啦。

  • 4.我們,是不是還差一個(gè)事件的處理(onClick())。但是,我們寫(xiě)的這個(gè)框架,并不知道將來(lái)按鈕要具體執(zhí)行哪些操作呀?對(duì)于未知的東西我們?cè)撛趺刺幚砟???duì)啦!用動(dòng)態(tài)代理。

在這里插入圖片描述

那下面我們就來(lái)具體看看這個(gè)動(dòng)態(tài)代理該怎么寫(xiě)吧~

首先我們要自定義一個(gè)MyInvocationHandler類(lèi),因?yàn)橐淼恼鎸?shí)對(duì)象是activity中的click()方法,所以,我們需要兩個(gè)屬性,并在構(gòu)造函數(shù)中直接傳入,我們還知道最終會(huì)調(diào)用這個(gè)類(lèi)里的invoke()方法,而這里要執(zhí)行的應(yīng)該是真實(shí)對(duì)象要執(zhí)行的操作,所以此處直接調(diào)用activityMethod.invoke(activity,objects);

//代理的是 new View.OnClickLisener()對(duì)象
//并且最終執(zhí)行的是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);
    }
}

好了,那我們就把第五步的步驟也補(bǔ)充上去吧:

private static void injectEvent(Object context) {
        Class<?> clazz = context.getClass();
        //1.獲取該activity上的所有方法
        Method[] methods = clazz.getDeclaredMethods();

        //2.循環(huán)遍歷方法,拿到每一個(gè)方法上的所有注解
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            //3.循環(huán)遍歷注解,拿到注解類(lèi)型對(duì)應(yīng)的Class,通過(guò)class去找,看看有沒(méi)有BaseEvent
            for (Annotation annotation : annotations) {
                //拿到注解類(lèi)型對(duì)應(yīng)的Class
                Class<?> annotationClass = annotation.annotationType();
                //通過(guò)Class去找,看看有沒(méi)有BaseEvent
                BaseEvent baseEvent = annotationClass.getAnnotation(BaseEvent.class);

                //如果沒(méi)有BaseEvent,則表示當(dāng)前方法不是一個(gè)事件處理的方法
                if (baseEvent == null) {
                    continue;
                }

                //4.如果有BaseEvent,則表示是事件處理的方法,那我們就去拿事件的三要素
                //拿到三要素
                Class<?> eventType = baseEvent.enventType();
                String setterMethodStr = baseEvent.setterMethod();
                String callbackMethod = baseEvent.callbackMethod();

                //5.接下來(lái)我們要反射執(zhí)行
//                   btn1.setOnClickListener(new View.OnClickListener() {
//                        @Override
//                        public void onClick(View view) {
//
//                        }
//                    });

                //5.1首先我們需要拿到事件源,(即對(duì)應(yīng)的view控件,此處即指btn按鈕)
                try {
                    //先獲取注解中的value方法,即 OnClick中的value()
                    Method valueMethod = annotationClass.getDeclaredMethod("value");
                    //再反射執(zhí)行 OnClick注解的 value()方法,得到id
                    int[] ids = (int[]) valueMethod.invoke(annotation);
                    for (int id : ids) {
                        //反射執(zhí)行context.findViewById(id)得到對(duì)應(yīng)的view
                        Method findViewByIdMethod = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewByIdMethod.invoke(context, id);
                        if (view == null) {
                            continue;
                        }

                        //5.2要拿到事件(即[new View.OnClickListener()]),這個(gè)我們通過(guò)上面的BaseEvent已經(jīng)得到了,即eventTpye。
                        //5.3拿到訂閱關(guān)系(即setOnClickListener()),即根據(jù)setterMethodStr得到setterMethod

                        //5.4動(dòng)態(tài)代理了  //activity==context    click===method
                        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(context, method);
                        Object proxy = Proxy.newProxyInstance(eventType.getClassLoader(),
                                new Class[]{eventType}, myInvocationHandler);

                        //  讓proxy執(zhí)行的click()
                        //參數(shù)1  setOnClickListener()的名稱(chēng)
                        //參數(shù)2  new View.OnClickListener()對(duì)象
                        Method setterMethod = view.getClass().getMethod(setterMethodStr, eventType);
                        // 反射執(zhí)行  view.setOnClickListener(new View.OnClickListener())
                        setterMethod.invoke(view, proxy);
                        //這時(shí)候,點(diǎn)擊按鈕時(shí)就會(huì)去執(zhí)行代理類(lèi)中的invoke方法()了
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }

好了,到這里我們所有的事件注入工作就都完成了,趕快在測(cè)試一下吧:

//在MainActivity中進(jìn)行使用測(cè)試
@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;
    }
}

//增加一個(gè)長(zhǎng)按事件,注意這里的方法返回類(lèi)型要和系統(tǒng)中的保持一致
@OnLongClick({R.id.button1, R.id.button2})
public boolean longClick(View view) {
    switch (view.getId()) {
        case R.id.button1:
            Toast.makeText(this, "好好學(xué)習(xí)", Toast.LENGTH_SHORT).show();
            break;
        case R.id.button2:
            Toast.makeText(this, "天天向上", Toast.LENGTH_SHORT).show();
            break;
    }
    return false;
}

運(yùn)行結(jié)果:

在這里插入圖片描述
在這里插入圖片描述
源代碼下載地址:https://github.com/junmei520/iocStudy

積累點(diǎn)滴,做好自己~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容