IOC技术-运行时注入(View注入,Event注入的原理)

什么是IOC?

官方定义:IOC是原来由程序中主动获取的资源,转变为由第三方提供,并有原来的程序被动接受的方式,已达到解耦的效果.

当然,这种以专业名词解释专业名词的方式,看了还是一头雾水.

直白点说:Inversion of control ,直接翻译过来就是控制翻转,这个理论主要用于实现对象之间的解耦.

也叫依赖注入,就是IOC容器在运行过程中,动态的将某种依赖关系注入到对象之中.

比如说对象A依赖对象B,通常我们使用new关键字来实现两个对象之间组合,这样耦合性就很高,引入IOC之后,当A对象需要B对象时,IOC容器就会创建一个对象B送给对象A,而A对象不需要去关心B对象是怎么创建的,该怎么销毁,这些都由IOC去完成.


IOC最核心的技术就是反射.

Android中有不少应用了IOC技术的框架,如xUtils,EventBus.

这里不是介绍那些框架怎么使用, 而是以一种简单的方式,来理解这些的框架的思想.理解了它的思想,不管是去使用,还是去开发新的框架,都会更容易打开思路.

下面结合代码,来看运行时注入,在android中是怎么使用的.

首先,看View的注入,也包括layout,contentView的注入,代码中加了注释,应该很容易看明白,

//定义一个类似IOC容器的类,这个类通常是一个基类中使用,比如在BaseActivity中使用,
// 那么所以继承自BaseActivity的类都具有了这个功能

//这个工具类中实现了,怎样去注入一个view,及注入布局资源。

public class IOCInjectUtils {
    public static void inject(Object context) {
        injectLayout(context);
        injectView(context);
    }

    private static void injectLayout(Object context) {
        //通过反射去调用MainActivity的setContentView。
        int layoutId = 0;
        Class<?> mainActivityClass = context.getClass();
        //通过MainActivity上的注解,拿到布局id
        InjectContentView injectContentView = mainActivityClass.getAnnotation(InjectContentView.class);
        if (null != injectContentView) {
            layoutId = injectContentView.value();
            //反射调用setContentView
            try {
                Method method = mainActivityClass.getMethod("setContentView", int.class);
                method.invoke(context, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void injectView(Object context) {
        Class<?> mainActivityClass = context.getClass();
        //拿到MainActivity中所有的属性变量
        Field[] fields = mainActivityClass.getDeclaredFields();
        for (Field field : fields) {
            //找到被InjectViewComponent注解过的组件
            InjecViewComponent injecViewComponent = field.getAnnotation(InjecViewComponent.class);
            if (null != injecViewComponent) {
                int componentId = injecViewComponent.value();
                try {
                    //通过反射调用findViewById为组件赋值
                    Method method = mainActivityClass.getMethod("findViewById", int.class);
                    View view = (View) method.invoke(context, componentId);
                    field.setAccessible(true);
                    //修改MainActivity中控件的值为view对象。
                    field.set(context, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

这个工具类,在基类中使用。

public class IOCBaseActivity  extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        IOCInjectUtils.inject(this);
    }
}

MainActivity继承使用了注入工具的基础类,

//资源文件的注入
@InjectContentView(R.layout.activity_main)
public class MainActivity extends IOCBaseActivity {

   // 控件的注入
    @InjecViewComponent(R.id.button)
    Button btn;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过注入实现布局资源设置
        //setContentView(R.layout.activity_main);

        // 控件的注入
        btn.setText("IOC注入");
    }
}

对布局文件的注解,要写在类上的,对Button view的注解写在具体控件上,

这里定义了两个注解,InjectContentView用来注入布局文件,InjectViewComponent用来注入view

@Target(ElementType.TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjectContentView {
    int value();
}
@Target(ElementType.FIELD)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjecViewComponent {
    int value();
}

接着,看下对事件的注入是怎么实现的.

先看常规的事件处理代码:

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //
            }
        });

这段代码中,有几个关键因素必不可少,一,btn对象,事件作用对象,二,onClickListener这是一个什么类型的事件,三,onClick,具体处理事件的函数,四,setOnClickListener,绑定事件订阅关系的方法.

在事件注入时,这三个关键因素都是会变化的,所以,针对事件的注解的定义,要考虑到这几个因素.

这里用到了注解的多态,我们先定义一个基础的注解,让他作用在别的注解上面,也即是写在具体的事件注解类上面.

@Target(ElementType.ANNOTATION_TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface InjectBaseEvent {
    String listenerSetter();//表示订阅关系
    Class<?> listenerType();//表示事件本身
    String callbackMethod();//表示事件处理的方法名
}

定义一个针对事件类型的注解,基础的注解,可以作用在这个注解上,并且给事件相关的因素赋值。

//可能会有多个控件,同时应用了这样的事件注入,所以value属性定义成了数组。
@Target(ElementType.METHOD)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
@InjectBaseEvent( listenerSetter = "setOnClickListener",
                listenerType = View.OnClickListener.class,
                callbackMethod = "onClick")
public @interface InjectOnClick {
    int[] value() default -1;
}

在MainActivity中的使用方式,这里可以填入多个控件的id,

    @InjectOnClick(R.id.button)
    public void injectClickEvent(View view) {

    }

如果要添加对长按的注入,可以仿照InjectOnClick注解,同时修改相应的事件因素,这样就很容易增加多种类型的事件注入。这个做法就是注解的多态。

最后,看下在注入工具类中,是怎么实现事件注入的。我把注释加在了代码中,跟随代码更容易说清楚。

public class IOCInjectUtils {
    public static void inject(Object context) {
        //injectLayout(context);
        //injectView(context);
        injectEvent(context);
    }

    private static void injectEvent(Object context) {
        Class<?> mainActivityClass = context.getClass();
        //拿到所有的方法
        Method[] methods = mainActivityClass.getDeclaredMethods();
        for (Method method : methods) {
            //方法上所有的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                //拿到注解对应的class,来判断它是不是一个需要处理事件的方法
                Class<?> annotationClass = annotation.annotationType();
                InjectBaseEvent annotationBaseEvent = annotationClass.getAnnotation(InjectBaseEvent.class);
                //如果没有InjectBaseEvent注解,说明它不是一个需要处理事件的方法。
                if (null == annotationBaseEvent) {
                    continue;
                }
                //否则获取事件处理的因素,确定是那种事件
                String listenerSetter = annotationBaseEvent.listenerSetter();
                Class<?> listenerType = annotationBaseEvent.listenerType();
                String callbackMethod = annotationBaseEvent.callbackMethod();

                Method value = null;
                try {
                    //拿到注解(如InjectOnClick注解)中的value方法,
                    // 是为了拿到value中都有哪些控件ID,需要应用这个注解。
                    value = annotationClass.getDeclaredMethod("value");
                    //获取控件id
                    int[] viewIds = (int[])value.invoke(annotation);
                    for (int viewId : viewIds) {
                        //拿到id对应的控件对象, 如Button btn
                        Method findViewById = mainActivityClass.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(context,viewId);
                        //下面就可以执行控件对象身上的事件处理方法了,如onClick方法,
                        // 但是因为onClick中,具体的业务代码是未知的,不确定的,
                        // 所以这里用到了动态代理,去执行程序员定义的业务代码,
                        // 让这个动态代理去告诉MainActivity,有事件(如onClick)被执行了
                        if (null == view) {
                            continue;
                        }
                        //现在让动态代理跟事件处理的方法(如onClick)绑定起来,
                        // 然后让代理类去执行MainActivity中真实的业务处理方法,如果injectClickEvent
                        ListenerInvocationHandler listenerInvocationHandler =
                                new ListenerInvocationHandler(context, method);
                        //生成代理对象,代理对象,代理的是View.OnClickListener,
                        // 执行的它的onClick方法
                        Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(),
                                new Class[]{listenerType}, listenerInvocationHandler);
                        //让proxy去执行View上的onClick方法。
                        //viewMethod拿到的是button控件上的setOnClickListener方法,
                        Method viewMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        //执行控件button的setOnClickListener方法,
                        // 然后由proxy执行MainActivity中使用事件注解的方法,
                        // 也就是MainActivity中自定义的injectClickEvent方法
                        viewMethod.invoke(view, proxyInstance);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

下面是动态代理类的实现代码:

//让这个代理类去实现 事件本身定义的接口,如View.OnClicklistener,
// 执行接口中方法,也即是事件处理中的方法,如onClick
//在事件处理方法中(onClick),实际要执行的是MainActivity中的injectClickEvent这个自定义的方法,
// 也即是添加了注解@InjectOnClick(R.id.button)
public class ListenerInvocationHandler implements InvocationHandler {
    //要执行的是那个类中的那个方法,如果MainActivity中的injectClickEvent方法,
    private Object activity;
    private Method methodInActivity;

    public ListenerInvocationHandler(Object activity, Method methodInActivity) {
        this.activity = activity;
        this.methodInActivity = methodInActivity;
    }

    //这里要执行的实际是事件处理方法,如onClick,也即是说,当点击button时,
    // framework层执行的onClick方法将会跳转到这里来,
    // 所以我们的注入框架中,会去处理invoke和onClick的关联
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这里去调用真实要去执行的方法,如MainActivity中的injectClickEvent。
        return methodInActivity.invoke(activity, args);
    }
}

到这里就完成了了事件注入的代码实现。

 

IOC概念的理解,参考博文:

https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html

https://segmentfault.com/a/1190000014803412

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值