在android開發中咱們常常用到xUtils,eventBus,ButterKnife等第三方框架來幫助咱們快捷的實現佈局綁定,數據傳遞等功能,對於這類框架的使用相信大多數的開發者都已輕車熟路,但對於其中的實現機制,可能還不是很熟悉。今天咱們來簡單解析下這類框架的內部實現機制,並實現相似xUtils中的佈局,控件,事件注入功能。android
對於這類框架其實內部都是用IOC注入技術實現的:git
IOC是原來由程序代碼主動獲取的資源,轉變由第三方獲取並使原來的代碼被動接收的方式,以達到解耦的效果,稱爲控制反轉;
複製代碼
IOC技術有三種類型: 1.運行時注入 xUtils,eventBus,springMVC 2.源碼時注入 android studio插件 3.編譯時注入 butterknife dagger2;github
少說多寫,下面咱們來用代碼來實現一個簡單的佈局注入: 一般狀況下咱們在新建Activity時,自動會使用:spring
setContentView(R.layout.activity_main);
複製代碼
進行佈局綁定。而如今咱們但願作到的是使用框架
@ContentView(R.layout.activity_main)
複製代碼
這種以註解的形式進行綁定佈局;ide
1.首先咱們新建一個InjectUtils工具類:函數
public class InjectUtils {
public static void inject(Object context){
//佈局的注入
injectLayout(context);
}
複製代碼
2.新建註解工具
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
複製代碼
注:@Target指的是註解的做用目標佈局
咱們的註解目標是類(指的是activity)因此此處選擇的是 ElementType.TYPEthis
@Retention:註解的保留位置
這裏選擇的是運行時註解
還須要注意的是註解的寫法@interface,和接口寫法一字之差,int value()是指定註解的裏值的類型;
而後咱們回到InjectUtils裏
private static void injectLayout(Object context) {
int layoutId = 0;
//經過反射拿到須要注入的Activity;
Class<?> clazz = context.getClass();
//在clazz上面去執行setContentView
ContentView contentView = clazz.getAnnotation(ContentView.class);
//獲取註解括號後面的內容;
if (contentView != null){
layoutId= contentView.value();
//反射去執行setContentView;
try {
//獲取反射類裏的方法;
Method method = context.getClass().getMethod("setContentView",int.class);
//調用方法
method.invoke(context,layoutId);
}catch (Exception e){
e.printStackTrace();
}
}
}
複製代碼
這樣咱們就能夠在activity裏進行引用了;
接下來咱們在實現我控件綁定,這裏的步驟和佈局綁定同樣:
1.新建註解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
複製代碼
2.一樣在InjectUtils裏寫上控件注入的方法:
//控件的注入;
private static void injectView(Object context) {
Class<?> clazz = context.getClass();
//經過註解獲取到反射類的全部的字段(此處是Activity中全部的控件)
Field[] fields= clazz.getDeclaredFields();
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null){
//獲取控件的id
int valued = viewInject.value();
//反射執行findViewById方法;
try {
Method method = clazz.getMethod("findViewById",int.class);
View view = (View) method.invoke(context,valued);
//AccessibleTest類中的成員變量爲private,故必須進行此操做
field.setAccessible(true);
field.set(context,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
複製代碼
這樣咱們就能夠在activity中使用了:
@ViewInject(R.id.btn1)
Button btn1;
@ViewInject(R.id.btn2)
Button btn2;
複製代碼
最後咱們來實現一個稍微有點難度的,事件註解 先看下咱們要實現的效果:
@OnClick({R.id.btn1,R.id.btn2})
public void onclick(View view){
}
@OnLongClick({R.id.btn2})
public boolean onLongClick(View view){
return false;
}
複製代碼
這裏看到咱們這裏使用了不一樣的事件註解,因此在這裏咱們須要考慮的是android中23種事件,我不可能在InjectUtils類中寫23個方法去實現這些事件;這裏採用的是註解的多態,和代理來實現事件的注入。來看下咱們具體的實現步驟:
1.須要建立一個EventBase註解:
//此處使用ANNOTATION_TYPE,表示該註解在其餘註解之上
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//setOnClickListener 訂閱關係
String listenerSetter();
//new View.OnClickListener() 事件自己
Class<?> listenerType();
//3.事件處理程序
String callbackMethod();
}
複製代碼
建立具體的事件註解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener"
,listenerType = View.OnClickListener.class
,callbackMethod = "OnClick")
public @interface OnClick {
int[] value() default -1; //此處表示賦值-1;
}
複製代碼
3.建立代理類
public class ListenerInvocationHandler implements InvocationHandler {
//須要在onClick中執行Activity.click();
private Object activity;
private Method activityMethod;
public ListenerInvocationHandler(Object activity, Method activityMethod) {
this.activity = activity;
this.activityMethod = activityMethod;
}
/**
* 表示onClick的執行;
* 程序執行onClick方法時,就會轉到這裏;
* 框架中不直接執行onClick;
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//此處調用被註解的click();
return activityMethod.invoke(activity,args);
}
}
複製代碼
這個類用來代理,new View.OnClickListener()對象; 並執行這個對象的onClick方法
4.在InjectUtils中處理具體的事件:
private static void injectClick(Object context) {
//須要一次性注入android的23種事件;
Class<?> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods)
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations){
//annotation 是事件好比onClick 就去取對應的註解;
Class<?> annotationClass = annotation.annotationType();
EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
//若是沒有eventBase 則表示當前方法不是一個事件處理的方法
if (eventBase == null)
//不是事件處理方法;
continue;
//開始獲取事件處理的相關信息;
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
//事件處理程序
String callBackMethod = eventBase.callbackMethod();
Method valueMethod = null;
try {
//反射獲得Id,再根據ID號獲得對應的VIEW;
valueMethod = annotationClass.getDeclaredMethod("value");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
//爲了獲得Button對象;
Method findViewById = clazz.getMethod("findViewById",int.class);
View view = (View) findViewById.invoke(context,id);
if (view == null){
continue;
}
//activity == context; click = method;
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context,method);
//創建代理關係;
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType},listenerInvocationHandler);
//讓proxy去執行的onClick();
Method onClickMethod = view.getClass().getMethod(listenerSetter,listenerType);
onClickMethod.invoke(view,proxy);
//此時,點擊按鈕就會去執行代理中invoke,方法;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
複製代碼
這樣咱們就能夠實現事件綁定了; 這裏只是IOC的簡單實現。