IOC注入技術運行時注入技術(1)

在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指的是註解的做用目標佈局

  • @Target(ElementType.TYPE)//接口、類、枚舉、註解
  • @Target(ElementType.FIELD) //字段、枚舉的常量
  • @Target(ElementType.METHOD) //方法
  • @Target(ElementType.PARAMETER) //方法參數
  • @Target(ElementType.CONSTRUCTOR) //構造函數
  • @Target(ElementType.LOCAL_VARIABLE)//局部變量
  • @Target(ElementType.ANNOTATION_TYPE)//註解
  • @Target(ElementType.PACKAGE) ///包

咱們的註解目標是類(指的是activity)因此此處選擇的是 ElementType.TYPEthis

@Retention:註解的保留位置

  • @Retention(RetentionPolicy.SOURCE) //註解僅存在於源碼中,在class字節碼文件中不包含
  • @Retention(RetentionPolicy.CLASS) // 默認的保留策略,註解會在class字節碼文件中存在,但運行時沒法得到
  • @Retention(RetentionPolicy.RUNTIME) // 註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到

這裏選擇的是運行時註解

還須要注意的是註解的寫法@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();
}
複製代碼
  1. 建立具體的事件註解:

    @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的簡單實現。

代碼地址:github.com/terry9309/I…

相關文章
相關標籤/搜索