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…

相关文章
相关标签/搜索