揭秘IOC架构设计

IOC(Inversion of Control,缩写为IoC) 控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度)。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。

布局注入

其实就是通过当前类,获取类的注解值然后通过反射设置到setContentView方法中,代码非常简单

/**
     * 注入setContentView
     * @param context
     */
    private static void injectLayout(Object context) {
        int layoutId = 0;
        Class<?> aClass = context.getClass();
        //获取类上的注解
        ContentView contentView = aClass.getAnnotation(ContentView.class);
        if (contentView != null) {
            layoutId = contentView.value();
            try {
                Method setContentView = context.getClass().getMethod("setContentView", int.class);
                setContentView.setAccessible(true);
                setContentView.invoke(context, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

我们只需要在activity中加入注解即可,这样我们就将布局注入到Activity中去了,避免了重复的操作.

@ContentView(value = R.layout.activity_main)
public class TestActivity extends BaseActivity

控件注入

和上述类似,一个Activity可能有几十个控件,那我们就需要几十次去findViewById,如何简化这种操作呢,其实就是获取类的所有属性,然后拿到设置自定义注解的属性,获取注解值,然后设置到findViewById中去.代码大家可以想想自己如何写? 代码如下

/**
     * 注入findViewById 获取类的所有属性 然后得到相应的注解
     * @param context
     */
    private static void injectView(Object context) {
        //获取成员变量的所有注解
        Class<?> aClass = context.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                int valueId = viewInject.value();
                try {
                    Method method = aClass.getMethod("findViewById", int.class);
                    View view = (View) method.invoke(context, valueId);
                    field.setAccessible(true);
                    field.set(context, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
 @ViewInject(value = R.id.text1)
 private TextView textView;

如上代码,这样我们就大大的简化了重复的操作,

事件注入,支持23种事件

如何简化多种事件呢? 支持事件的动态扩展简化事件操作,我们都知道一个事件由:订阅者 事件源 事件回调 三部分组成.

首先,我们需要一个基本的注解,注解的多态对注解的扩展,事件注解

@Retention(RetentionPolicy.RUNTIME)
//该注解在另外一个注解上使用
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {

    //setOnClickListener 订阅者
    String listenerSetter();

    //事件源 类型 OnClickListener
    Class<?> listenerType();

    //事件 回调方法
    String callbackMethod();

}

然后我们基于基本的注解去扩展多个事件,如下点击事件,如果要实现其他的事件只需要新建一个注解类覆写EventBase添加事件三要素.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
        listenerType = View.OnClickListener.class,
        callbackMethod = "onClick")
public @interface OnClick {
    int[] value() default -1;
}

再比如长按事件

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
        listenerType = View.OnLongClickListener.class,
        callbackMethod = "onLongClick")
public @interface OnLongClick {
    int[] value() default -1;
}

接下来就是最核心的如何注入事件,首先我们要获取类的所有方法上的注解,由于是判断多种注解不能够写死,我们可以判断可以找到EventBase 因为所有的事件都有EventBase.然后获取事件三要素,根据注解的viewid 获取到view,viewid 可以注入多个,拿到view后然后注入事件,代码如下

 Class<?> aClass = context.getClass();
        Method[] methods = aClass.getDeclaredMethods();//获取方法
        for (Method method : methods) {
            //获取方法上的注解 由于是多种注解不能够写死
            //可以找到EventBase 因为所有的事件都有EventBase
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                //判断注解的类型
                if (eventBase == null) {
                    continue;
                }
                //获取事件三要素
                String listenerSetter = eventBase.listenerSetter();
                Class<?> listenerType = eventBase.listenerType();
                String callbackMethod = eventBase.callbackMethod();
                //注入事件 根据view id 获取到view 但是现在不能够写死一个注解 因为有多种事件注解类型 如果拿到呢?
                Method valueMethod;
                try {
                    valueMethod = annotationType.getDeclaredMethod("value");
                    int[] viewIds = (int[]) valueMethod.invoke(annotation);
                    if (viewIds != null) {
                        for (int viewId : viewIds) {
                            View view;
                            if (viewMaps.containsKey(viewId)) {
                                Log.e(TAG, "injectClick: 仓库中存在此view,直接从仓库中获取");
                                view = viewMaps.get(viewId);
                            } else {
                                Log.e(TAG, "injectClick: 仓库中不存在此view,通过反射获取,存储到仓库中");
                                //获取到view
                                Method findViewByIdMethod = aClass.getMethod("findViewById", int.class);
                                view = (View) findViewByIdMethod.invoke(context, viewId);
                                viewMaps.put(viewId, view);//存储view
                            }
                            if (view == null)
                                continue;
                            //注入view事件
                            //获取事件的方法
                            Method listenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                            //执行事件方法 method
                            ListenerInvocationHandler invocationHandler =
                                    new ListenerInvocationHandler(context, method, callbackMethod);
                            //通过动态代理实现listenerType接口类
                            Object proProxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, invocationHandler);
                            listenerMethod.invoke(view, proProxy);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
 @OnLongClick(value = {R.id.text1})
    public boolean test(View view) {
        Toast.makeText(this, "注入长按事件完成", Toast.LENGTH_LONG).show();
        return false;
    }