IOC實現ButterKnife(二)之注入事件

這是IOC的第二篇文章,第一篇請看IOC實現ButterKnife(一)之注入佈局與注入控件

由於是剛開始寫文章,所以多少有點亂,不好意思。

1、遇到的問題

	由於每一個按鈕的點擊事件都需要寫一個setonClickListener的代碼,這樣造成了代碼冗餘且耦合度很高,因此未了解決這些問題,採用註解的方法來進行統一的處理。

2、想實現效果如下:

在這裏插入圖片描述

3、需要的知識

	1、事件三要素:例如
   btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

按鈕的點擊事件的代碼如上,該事件包含事件的三個要素:
要素一:setOnClickListener(訂閱事件的方式)
要素二:View.OnClickListener(訂閱的事件)
要素三:onClick(消費事件的方法)
任何一個事件都包含這三樣東西

	2、反射
	3、註解:使用註解之上的註解
	4、代理Proxy的使用:通過代理的方式去監聽事件源View.OnClickListener中onClick方法的執行

4、實現過程

1、思考:由於一個對象可以有多個事件如點擊事件、長按事件等,因此抽出一個公共的註解(CommonBase)來存儲事件的三要素:

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CommonBase {
    //以setOnclickListener爲例
    String setCommonListener();//存儲setOnclickListener名稱,用於反射該方法
    Class setCommonObject();//存儲View.OnClickListener類,用於反射setOnclickListener時傳入參數的類型,並通過代理監聽該接口
    String setCommonMethod();//消費的方法,在此暫時沒用到

}

2、創建點擊的事件註解(CommonOnClick),並通過註解上的註解來指定其對應的三要素

@Target(ElementType.METHOD)//作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
//爲該註解指定特定的三要素,相當於讓CommonOnClick與OnClickListener事件進行綁定
@CommonBase(setCommonListener = "setOnClickListener",setCommonObject = View.OnClickListener.class,setCommonMethod = "onClick")
public @interface CommonOnClick {
    int[] value();//因爲很多控件都有點擊事件,所以用數組接收
}

3、新建一個InjectTool工具類並創建injectCommonListener方法對事件注入進行處理,實現的思路如下:

	1、首先拿到MainActivity對象
	2、通過1拿到該對象的類信息clazz
	3、通過clazz拿到該類中的所有方法declaredMethods並遍歷所有方法獲取到單個方法declaredMethod。
	4、通過3獲取到的單個方法declaredMethod,並遍歷該方法上的所有註解annotations,以獲得單個註解annotation
	5、判斷單個註解annotation之上的註解中有無我們定義三要素的公共註解CommonBase,如果沒有則退出,如果有則繼續
	6、遍歷到有CommonBase的註解,並獲取其中的三要素事件:要素一(訂閱方式):commonListener   要素二(事件源): commonObject    要素三(消費事件的方法):commonMethod
	7、獲取4中的單個註解中傳入的素組value值,由於該註解是動態的,代表該註解的名稱不能確定,但是註解的方法都是確定的,因爲都是同一個  int[] value();,所以這裏可以通過反射反射出value的值
	8、利用發射獲取素組value[]值
	8、遍歷value[]值,以獲取到單個value
	9、通過反射執行findViewById,並將value傳入以得到invokeObject對象
	10、通過反射獲取invokeObject對象中的commonListener方法(realCommonMethod),並傳入參數commonObject
	11、通過代理Proxy去監聽commonObject的執行
	12、執行realCommonMethod方法並將代理proxy賦值進去。



	代碼如下:
    private static void injectCommonListener(final Object object) {
        Class<?> clazz = object.getClass();
        //獲取所有方法,並遍歷    getDeclaredMethods可獲取到當前對象的private protected等,而  clazz.getMethod()只能獲取到public的,單其包括父類的public
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (final Method declaredMethod : declaredMethods) {
            //獲取方法上的所有註解並遍歷
            Annotation[] annotations =
                    declaredMethod.getAnnotations();
            for (Annotation annotation : annotations) {
                //判斷該註解上有無CommonBase註解
                CommonBase commonBase = annotation.annotationType().getAnnotation(CommonBase.class);
                if (commonBase==null){
                    //如果沒有註解則直接跳過
                    continue;
                }
                //已找到CommonBase註解,獲取三要素
                String commonListener = commonBase.setCommonListener();
                Class commonObject = commonBase.setCommonObject();
                String commonMethod = commonBase.setCommonMethod();
                //獲取註解值

                try {
                    //通過反射獲取註解annotation中的value值並遍歷
                    Method valueMethod = annotation.getClass().getDeclaredMethod("value");
                    int[] values= (int[]) valueMethod.invoke(annotation);
                    for ( int value : values) {
                        //獲取到id後通過反射獲取findViewById方法
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        //執行findViewById
                        Object invokeObject = findViewById.invoke(object, value);
                        //通過反射獲取到invokeObject對象中的commonListener方法
                        Method realCommonMethod = invokeObject.getClass().getMethod(commonListener, commonObject);
                        final int ff = value;
                        //採用動態代理監聽commonObject接口的執行,因爲當用戶執行commonObject事件時,一定會執行commonObject接口,而動態代理剛好可以監聽到
                        Object proxy = Proxy.newProxyInstance(commonObject.getClassLoader(), new Class[]{commonObject}, new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                args[0] = ff;
                                //當監聽到用戶點擊了事件後,去執行我們自己的declareMethod方法
                                return declaredMethod.invoke(object,args);
                            }
                        });
                        //將代理賦值給我們的realCommonMethod對象
                        realCommonMethod.invoke(invokeObject,proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

demo地址:https://github.com/okpksbzhang/IOCDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章