1. 程式人生 > >Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)

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這個框架,畢竟想很完善的實現一個完整的注入不是一兩篇部落格就可以搞定,但是核心和骨架已經實現了~~大家有興趣的可以繼續去完善~