Android 進階 教你打造 Android 中的 IOC 架構 【ViewInject】 (下),androidviewinject
上一篇部落格我們已經帶大家簡單的吹了一下IoC,實現了Activity中View的布局以及控制項的注入,如果你不瞭解,請參考:Android 進階 教你打造 Android 中的 IOC 架構 【ViewInject】 (上)。
本篇部落格將帶大家實現View的事件的注入。
1、目標效果
上篇部落格,我們的事件的代碼是這麼寫的:
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;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);ViewInjectUtils.inject(this);mBtn1.setOnClickListener(this);mBtn2.setOnClickListener(this);}@Overridepublic 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,然後實現匿名類或者別的方式神馬的,我們改變為:
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、註解檔案
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();}
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的某個方法上的:
@OnClick({ R.id.id_btn, R.id.id_btn02 })public void clickBtnInvoked(View view)
如果你還記得,上篇部落格我們的ViewInjectUtils.inject(this);裡面已經有了兩個方法,本篇多了一個:
public static void inject(Activity activity){injectContentView(activity);injectViews(activity);injectEvents(activity);}
2、injectEvents
/** * 注入所有的事件 * * @param activity */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);//如果設定為EventBaseif (eventBaseAnnotation != null){//取出設定監聽器的名稱,監聽器的類型,調用的方法名String listenerSetter = eventBaseAnnotation.listenerSetter();Class<?> listenerType = eventBaseAnnotation.listenerType();String methodName = eventBaseAnnotation.methodName();try{//拿到Onclick註解中的value方法Method aMethod = annotationType.getDeclaredMethod("value");//取出所有的viewIdint[] viewIds = (int[]) aMethod.invoke(annotation, null);//通過InvocationHandler設定代理DynamicHandler handler = new DynamicHandler(activity);handler.addMethod(methodName, method);Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[] { listenerType }, handler);//遍曆所有的View,設定事件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的實作類別:
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);}@Overridepublic 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、關於代理
那麼,本文結束了麼,沒有~~~關於以下幾行代碼,相信大家肯定有困惑,這幾行幹了什嗎?
//通過InvocationHandler設定代理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
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介面
package com.zhy.invocationhandler;public interface OnClickListener{void onClick();}
OnClickListenerHandler , InvocationHandler的實作類別
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);}@Overridepublic 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
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中的代碼:
//通過InvocationHandler設定代理DynamicHandler handler = new DynamicHandler(activity);//往map添加方法handler.addMethod(methodName, method);Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[] { listenerType }, handler);
是不是和我們init中的類似~~
好了,關於如何把介面的回調和我們Activity裡面的方法關聯上我們也解釋完了~~~
註:部分代碼參考了xUtils這個架構,畢竟想很完善的實現一個完整的注入不是一兩篇部落格就可以搞定,但是核心和骨架已經實現了~~大家有興趣的可以繼續去完善~
源碼點擊下載
android常用設計模式或者架構有什?我說的是最常用的,像j2ee中的SSH架構
android內暫時沒有什麼架構。
自己按照mvc分層的原則做好就好,有些開源的像facebook也有很多的開源庫。你可以考慮一下使用。
熟悉Android應用與架構開發,希望從上到下走通Android系統 來個有對於android嵌入式底層開發的教程
百度文科、網易視頻學院、百度課程、優酷、51自學網裡搜尋,應該會有
各網站的公開課
------------------@選為滿意答案@------------------