上一篇博客我們已經帶大家簡單的吹了一下IoC,實現了Activity中View的布局以及控件的注入,如果你不了解,請參考:Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)。
本篇博客將帶大家實現View的事件的注入。
1、目標效果
上篇博客,我們的事件的代碼是這么寫的:
[java]?view plaincopy
package?com.zhy.zhy_xutils_test;?? ?? import?android.app.Activity;?? import?android.os.Bundle;?? import?android.view.View;?? import?android.view.View.OnClickListener;?? import?android.widget.Button;?? import?android.widget.Toast;?? ?? import?com.zhy.ioc.view.ViewInjectUtils;?? import?com.zhy.ioc.view.annotation.ContentView;?? import?com.zhy.ioc.view.annotation.ViewInject;?? ?? @ContentView(value?=?R.layout.activity_main)?? public?class?MainActivity?extends?Activity?implements?OnClickListener?? {?? ????@ViewInject(R.id.id_btn)?? ????private?Button?mBtn1;?? ????@ViewInject(R.id.id_btn02)?? ????private?Button?mBtn2;?? ?? ????@Override?? ????protected?void?onCreate(Bundle?savedInstanceState)?? ????{?? ????????super.onCreate(savedInstanceState);?? ?????????? ????????ViewInjectUtils.inject(this);?? ?? ????????mBtn1.setOnClickListener(this);?? ????????mBtn2.setOnClickListener(this);?? ????}?? ?? ????@Override?? ????public?void?onClick(View?v)?? ????{?? ????????switch?(v.getId())?? ????????{?? ????????case?R.id.id_btn:?? ????????????Toast.makeText(MainActivity.this,?"Why?do?you?click?me??",?? ????????????????????Toast.LENGTH_SHORT).show();?? ????????????break;?? ?? ????????case?R.id.id_btn02:?? ????????????Toast.makeText(MainActivity.this,?"I?am?sleeping?!!!",?? ????????????????????Toast.LENGTH_SHORT).show();?? ????????????break;?? ????????}?? ????}?? ?? }??
光有View的注入能行么,我們寫View的目的,很多是用來交互的,得可以點擊神馬的吧。摒棄傳統的神馬,setOnClickListener,然后實現匿名類或者別的方式神馬的,我們改變為:
[java]?view plaincopy
package?com.zhy.zhy_xutils_test;?? ?? import?android.view.View;?? import?android.widget.Button;?? import?android.widget.Toast;?? ?? import?com.zhy.ioc.view.annotation.ContentView;?? import?com.zhy.ioc.view.annotation.OnClick;?? import?com.zhy.ioc.view.annotation.ViewInject;?? ?? @ContentView(value?=?R.layout.activity_main)?? public?class?MainActivity?extends?BaseActivity?? {?? ????@ViewInject(R.id.id_btn)?? ????private?Button?mBtn1;?? ????@ViewInject(R.id.id_btn02)?? ????private?Button?mBtn2;?? ?? ????@OnClick({?R.id.id_btn,?R.id.id_btn02?})?? ????public?void?clickBtnInvoked(View?view)?? ????{?? ????????switch?(view.getId())?? ????????{?? ????????case?R.id.id_btn:?? ????????????Toast.makeText(this,?"Inject?Btn01?!",?Toast.LENGTH_SHORT).show();?? ????????????break;?? ????????case?R.id.id_btn02:?? ????????????Toast.makeText(this,?"Inject?Btn02?!",?Toast.LENGTH_SHORT).show();?? ????????????break;?? ????????}?? ????}?? ?? }??
直接通過在Activity中的任何一個方法上,添加注解,完成1個或多個控件的事件的注入。這里我把onCreate搬到了BaseActivity中,里面調用了ViewInjectUtils.inject(this);
2、實現
1、注解文件
[java]?view plaincopy
package?com.zhy.ioc.view.annotation;?? ?? import?java.lang.annotation.ElementType;?? import?java.lang.annotation.Retention;?? import?java.lang.annotation.RetentionPolicy;?? import?java.lang.annotation.Target;?? ?? @Target(ElementType.ANNOTATION_TYPE)?? @Retention(RetentionPolicy.RUNTIME)?? public?@interface?EventBase?? {?? ????Class<?>?listenerType();?? ?? ????String?listenerSetter();?? ?? ????String?methodName();?? }??
[java]?view plaincopy
package?com.zhy.ioc.view.annotation;?? ?? import?java.lang.annotation.ElementType;?? import?java.lang.annotation.Retention;?? import?java.lang.annotation.RetentionPolicy;?? import?java.lang.annotation.Target;?? ?? import?android.view.View;?? ?? @Target(ElementType.METHOD)?? @Retention(RetentionPolicy.RUNTIME)?? @EventBase(listenerType?=?View.OnClickListener.class,?listenerSetter?=?"setOnClickListener",?methodName?=?"onClick")?? public?@interface?OnClick?? {?? ????int[]?value();?? }??
EventBase主要用于給OnClick這類注解上添加注解,畢竟事件很多,并且設置監聽器的名稱,監聽器的類型,調用的方法名都是固定的,對應上面代碼的:
listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick"
Onclick是用于寫在Activity的某個方法上的:
[java]?view plaincopy
@OnClick({?R.id.id_btn,?R.id.id_btn02?})?? ????public?void?clickBtnInvoked(View?view)??
如果你還記得,上篇博客我們的ViewInjectUtils.inject(this);里面已經有了兩個方法,本篇多了一個:
[java]?view plaincopy
public?static?void?inject(Activity?activity)?? ????{?? ????????injectContentView(activity);?? ????????injectViews(activity);?? ????????injectEvents(activity);?? ????}??
2、injectEvents
[java]?view plaincopy
? ? ? ? ?? ????private?static?void?injectEvents(Activity?activity)?? ????{?? ?????????? ????????Class<??extends?Activity>?clazz?=?activity.getClass();?? ????????Method[]?methods?=?clazz.getMethods();?? ?????????? ????????for?(Method?method?:?methods)?? ????????{?? ????????????Annotation[]?annotations?=?method.getAnnotations();?? ?????????????? ????????????for?(Annotation?annotation?:?annotations)?? ????????????{?? ????????????????Class<??extends?Annotation>?annotationType?=?annotation?? ????????????????????????.annotationType();?? ?????????????????? ????????????????EventBase?eventBaseAnnotation?=?annotationType?? ????????????????????????.getAnnotation(EventBase.class);?? ?????????????????? ????????????????if?(eventBaseAnnotation?!=?null)?? ????????????????{?? ?????????????????????? ????????????????????String?listenerSetter?=?eventBaseAnnotation?? ????????????????????????????.listenerSetter();?? ????????????????????Class<?>?listenerType?=?eventBaseAnnotation.listenerType();?? ????????????????????String?methodName?=?eventBaseAnnotation.methodName();?? ?? ????????????????????try?? ????????????????????{?? ?????????????????????????? ????????????????????????Method?aMethod?=?annotationType?? ????????????????????????????????.getDeclaredMethod("value");?? ?????????????????????????? ????????????????????????int[]?viewIds?=?(int[])?aMethod?? ????????????????????????????????.invoke(annotation,?null);?? ?????????????????????????? ????????????????????????DynamicHandler?handler?=?new?DynamicHandler(activity);?? ????????????????????????handler.addMethod(methodName,?method);?? ????????????????????????Object?listener?=?Proxy.newProxyInstance(?? ????????????????????????????????listenerType.getClassLoader(),?? ????????????????????????????????new?Class<?>[]?{?listenerType?},?handler);?? ?????????????????????????? ????????????????????????for?(int?viewId?:?viewIds)?? ????????????????????????{?? ????????????????????????????View?view?=?activity.findViewById(viewId);?? ????????????????????????????Method?setEventListenerMethod?=?view.getClass()?? ????????????????????????????????????.getMethod(listenerSetter,?listenerType);?? ????????????????????????????setEventListenerMethod.invoke(view,?listener);?? ????????????????????????}?? ?? ????????????????????}?catch?(Exception?e)?? ????????????????????{?? ????????????????????????e.printStackTrace();?? ????????????????????}?? ????????????????}?? ?? ????????????}?? ????????}?? ?? ????}??
嗯,注釋盡可能的詳細了,主要就是遍歷所有的方法,拿到該方法省的OnClick注解,然后再拿到該注解上的EventBase注解,得到事件監聽的需要調用的方法名,類型,和需要調用的方法的名稱;通過Proxy和InvocationHandler得到監聽器的代理對象,顯示設置了方法,最后通過反射設置監聽器。
這里有個難點,就是關于DynamicHandler和Proxy的出現,如果不理解沒事,后面會詳細講解。
3、DynamicHandler
這里用到了一個類DynamicHandler,就是InvocationHandler的實現類:
[java]?view plaincopy
package?com.zhy.ioc.view;?? ?? import?java.lang.ref.WeakReference;?? import?java.lang.reflect.InvocationHandler;?? import?java.lang.reflect.Method;?? import?java.util.HashMap;?? ?? public?class?DynamicHandler?implements?InvocationHandler?? {?? ????private?WeakReference<Object>?handlerRef;?? ????private?final?HashMap<String,?Method>?methodMap?=?new?HashMap<String,?Method>(?? ????????????1);?? ?? ????public?DynamicHandler(Object?handler)?? ????{?? ????????this.handlerRef?=?new?WeakReference<Object>(handler);?? ????}?? ?? ????public?void?addMethod(String?name,?Method?method)?? ????{?? ????????methodMap.put(name,?method);?? ????}?? ?? ????public?Object?getHandler()?? ????{?? ????????return?handlerRef.get();?? ????}?? ?? ????public?void?setHandler(Object?handler)?? ????{?? ????????this.handlerRef?=?new?WeakReference<Object>(handler);?? ????}?? ?? ????@Override?? ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?? ????????????throws?Throwable?? ????{?? ????????Object?handler?=?handlerRef.get();?? ????????if?(handler?!=?null)?? ????????{?? ????????????String?methodName?=?method.getName();?? ????????????method?=?methodMap.get(methodName);?? ????????????if?(method?!=?null)?? ????????????{?? ????????????????return?method.invoke(handler,?args);?? ????????????}?? ????????}?? ????????return?null;?? ????}?? }??
好了,代碼就這么多,這樣我們就實現了,我們事件的注入~~
效果圖:
效果圖其實沒撒好貼的,都一樣~~~
3、關于代理
那么,本文結束了么,沒有~~~關于以下幾行代碼,相信大家肯定有困惑,這幾行干了什么?
[java]?view plaincopy
?? ????????????????????????DynamicHandler?handler?=?new?DynamicHandler(activity);?? ????????????????????????handler.addMethod(methodName,?method);?? ????????????????????????Object?listener?=?Proxy.newProxyInstance(?? ????????????????????????????????listenerType.getClassLoader(),?? ????????????????????????????????new?Class<?>[]?{?listenerType?},?handler);??
InvocationHandler和Proxy成對出現,相信大家如果對Java比較熟悉,肯定會想到Java的動態代理~~~
關于InvocationHandler和Proxy的文章,大家可以參考:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/?ps:IBM的技術文章還是相當不錯的,畢竟有人審核還有獎金~
但是我們的實現有一定的區別,我為什么說大家疑惑呢,比如反射實現:
mBtn2.setOnClickListener(this);這樣的代碼,難點在哪呢?
1、mBtn2的獲取?so easy?
2、調用setOnClickListener ? so easy?
but , 這個 this,這個this是OnClickListener的實現類的實例,OnClickListener是個接口~~你的實現類怎么整,聽說過反射newInstance對象的,但是你現在是接口!
是吧~現在應該明白上述幾行代碼做了什么了?實現了接口的一個代理對象,然后在代理類的invoke中,對接口的調用方法進行處理。
4、代碼是最好的老師
光說誰都理解不了,你在這xx什么呢??下面看代碼,我們模擬實現這樣一個情景:
Main類中實現一個Button,Button有兩個方法,一個setOnClickListener和onClick,當調用Button的onClick時,觸發的事件是Main類中的click方法
涉及到4個類:
Button
[java]?view plaincopy
package?com.zhy.invocationhandler;?? ?? public?class?Button?? {?? ????private?OnClickListener?listener;?? ?? ????public?void?setOnClickLisntener(OnClickListener?listener)?? ????{?? ?? ????????this.listener?=?listener;?? ????}?? ?? ????public?void?click()?? ????{?? ????????if?(listener?!=?null)?? ????????{?? ????????????listener.onClick();?? ????????}?? ????}?? }??
OnClickListener接口
[java]?view plaincopy
package?com.zhy.invocationhandler;?? ?? public?interface?OnClickListener?? {?? ????void?onClick();?? }??
OnClickListenerHandler , InvocationHandler的實現類
[java]?view plaincopy
package?com.zhy.invocationhandler;?? ?? import?java.lang.reflect.InvocationHandler;?? import?java.lang.reflect.Method;?? import?java.util.HashMap;?? import?java.util.Map;?? ?? public?class?OnClickListenerHandler?implements?InvocationHandler?? {?? ????private?Object?targetObject;?? ?? ????public?OnClickListenerHandler(Object?object)?? ????{?? ????????this.targetObject?=?object;?? ????}?? ?? ????private?Map<String,?Method>?methods?=?new?HashMap<String,?Method>();?? ?? ????public?void?addMethod(String?methodName,?Method?method)?? ????{?? ????????methods.put(methodName,?method);?? ????}?? ?? ????@Override?? ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?? ????????????throws?Throwable?? ????{?? ?? ????????String?methodName?=?method.getName();?? ????????Method?realMethod?=?methods.get(methodName);?? ????????return?realMethod.invoke(targetObject,?args);?? ????}?? ?? }??
我們的Main
[java]?view plaincopy
package?com.zhy.invocationhandler;?? ?? import?java.lang.reflect.InvocationTargetException;?? import?java.lang.reflect.Method;?? import?java.lang.reflect.Proxy;?? ?? public?class?Main?? {?? ????private?Button?button?=?new?Button();?? ?????? ????public?Main()?throws?SecurityException,?IllegalArgumentException,?NoSuchMethodException,?IllegalAccessException,?InvocationTargetException?? ????{?? ????????init();?? ????}?? ?? ????public?void?click()?? ????{?? ????????System.out.println("Button?clicked!");?? ????}?? ?? ????public?void?init()?throws?SecurityException,?? ????????????NoSuchMethodException,?IllegalArgumentException,?? ????????????IllegalAccessException,?InvocationTargetException?? ????{?? ????????OnClickListenerHandler?h?=?new?OnClickListenerHandler(this);?? ????????Method?method?=?Main.class.getMethod("click",?null);?? ????????h.addMethod("onClick",?method);?? ????????Object?clickProxy?=?Proxy.newProxyInstance(?? ????????????????OnClickListener.class.getClassLoader(),?? ????????????????new?Class<?>[]?{?OnClickListener.class?},?h);?? ????????Method?clickMethod?=?button.getClass().getMethod("setOnClickLisntener",?? ????????????????OnClickListener.class);?? ????????clickMethod.invoke(button,?clickProxy);?? ?????????? ????}?? ?? ????public?static?void?main(String[]?args)?throws?SecurityException,?? ????????????IllegalArgumentException,?NoSuchMethodException,?? ????????????IllegalAccessException,?InvocationTargetException?? ????{?? ?? ????????Main?main?=?new?Main();?? ?????????? ????????main.button.click();?? ????}?? ?? }??
我們模擬按鈕點擊:調用main.button.click(),實際執行的卻是Main的click方法。
看init中,我們首先初始化了一個OnClickListenerHandler,把Main的當前實例傳入,然后拿到Main的click方法,添加到OnClickListenerHandler中的Map中。
然后通過Proxy.newProxyInstance拿到OnClickListener這個接口的一個代理,這樣執行這個接口的所有的方法,都會去調用OnClickListenerHandler的invoke方法。
但是呢?OnClickListener畢竟是個接口,也沒有方法體~~那咋辦呢?這時候就到我們OnClickListenerHandler中的Map中大展伸手了:
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
String methodName = method.getName();
Method realMethod = methods.get(methodName);
return realMethod.invoke(targetObject, args);
}
我們顯示的把要執行的方法,通過鍵值對存到Map里面了,等調用到invoke的時候,其實是通過傳入的方法名,得到Map中存儲的方法,然后調用我們預設的方法~。
這樣,大家應該明白了,其實就是通過Proxy得到接口的一個代理,然后在InvocationHandler中使用一個Map預先設置方法,從而實現Button的onClick,和Main的click關聯上。
現在看我們InjectEvents中的代碼:
[java]?view plaincopy
?? ????????????????????????DynamicHandler?handler?=?new?DynamicHandler(activity);?? ?????????????????????????? ????????????????????????handler.addMethod(methodName,?method);?? ????????????????????????Object?listener?=?Proxy.newProxyInstance(?? ????????????????????????????????listenerType.getClassLoader(),?? ????????????????????????????????new?Class<?>[]?{?listenerType?},?handler);??
是不是和我們init中的類似~~
好了,關于如何把接口的回調和我們Activity里面的方法關聯上我們也解釋完了~~~
注:部分代碼參考了xUtils這個框架,畢竟想很完善的實現一個完整的注入不是一兩篇博客就可以搞定,但是核心和骨架已經實現了~~大家有興趣的可以繼續去完善~
源碼點擊下載
總結
以上是生活随笔為你收集整理的Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。