這是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