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; @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,然後實現匿名類或者別的方式神馬的,我們改變為:
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);
//如果設定為EventBase
if (eventBaseAnnotation != null)
{
//取出設定監聽器的名稱,監聽器的型別,呼叫的方法名
String listenerSetter = eventBaseAnnotation
.listenerSetter();
Class<?> listenerType = eventBaseAnnotation.listenerType();
String methodName = eventBaseAnnotation.methodName();
try
{
//拿到Onclick註解中的value方法
Method aMethod = annotationType
.getDeclaredMethod("value");
//取出所有的viewId
int[] 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);
}
@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、關於代理
那麼,本文結束了麼,沒有~~~關於以下幾行程式碼,相信大家肯定有困惑,這幾行幹了什麼?
//通過InvocationHandler設定代理
DynamicHandler handler = new DynamicHandler(activity);
handler.addMethod(methodName, method);
Object listener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class<?>[] { listenerType }, handler);
InvocationHandler和Proxy成對出現,相信大家如果對Java比較熟悉,肯定會想到Java的動態代理~~~
但是我們的實現有一定的區別,我為什麼說大家疑惑呢,比如反射實現:
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);
}
@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
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這個框架,畢竟想很完善的實現一個完整的注入不是一兩篇部落格就可以搞定,但是核心和骨架已經實現了~~大家有興趣的可以繼續去完善~