IOC之运行时注入-实现Activity的布局注入+控件注入+事件绑定

个人博客

http://www.milovetingting.cn

IOC之运行时注入-实现Activity的布局注入+控件注入+事件绑定

前言

本文主要介绍基于IOC的设计原则,实现以下功能:

  • 布局注入

  • 控件注入

  • 事件注入

其实这些功能,在之前也有零散地介绍过,这里再做一个统一的整理。

这里暂时不考虑运行时反射的效率问题,只是展示一种实现方案。

IOC的定义

IOC,即Inversion of Control,意为控制反转,是面向对象编程中的一种设计原则,可以用来降低代码间的耦合。最常见的方式是依赖注入(Dependence Injection,简称DI)。通过IOC,对象在创建时,由外界来控制,而不是内部直接控制。

布局注入

平时,我们在Activity中,可能会通过在onCreate方法中调用setContentView的方法,给Activity绑定布局。而基于IOC,则可以通过注解来实现:

注解的定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutInject {
    /**
     * 布局id
     *
     * @return
     */
    @LayoutRes int value();
}

注解的使用

@LayoutInject(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtil.inject(this);
    }
}

在MainActivity上加上前面定义的LayoutInject注解,然后在onCreate中调用注入的方法InjectUtil.inject(this)

Inject方法

/**
     * 注入
     *
     * @param target
     */
    public static void inject(Object target) {
        if (target == null) {
            return;
        }
        injectLayout(target);
    }

    /**
     * 布局注入
     *
     * @param target 需要注入的组件
     */
    private static void injectLayout(Object target) {
        Class clazz = target.getClass();
        boolean annotationPresent = clazz.isAnnotationPresent(LayoutInject.class);
        if (!annotationPresent) {
            return;
        }
        LayoutInject annotation = clazz.getAnnotation(LayoutInject.class);
        int layoutId = annotation.value();
        try {
            Method method = clazz.getMethod("setContentView", int.class);
            method.invoke(target, layoutId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

控件注入

注解的定义

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    /**
     * 控件id
     *
     * @return
     */
    @IdRes int value();
}

注解的使用

 @ViewInject(R.id.btn1)
    Button btn1;

 @ViewInject(R.id.btn2)
    Button btn2;

Inject方法

/**
     * 注入
     *
     * @param target
     */
    public static void inject(Object target) {
        if (target == null) {
            return;
        }
        injectView(target);
    }

/**
     * 控件注入
     *
     * @param target 需要注入的组件
     */
    private static void injectView(Object target) {
        Class clazz = target.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            boolean annotationPresent = field.isAnnotationPresent(ViewInject.class);
            if (!annotationPresent) {
                continue;
            }
            ViewInject annotation = field.getAnnotation(ViewInject.class);
            int viewId = annotation.value();
            try {
                Method method = clazz.getMethod("findViewById", int.class);
                View view = (View) method.invoke(target, viewId);
                field.setAccessible(true);
                field.set(target, view);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

事件注入

注解的定义

事件类型的注解

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {

    /**
     * 设置listener的方法,如:setOnClickListener
     *
     * @return
     */
    String listenerSetter();

    /**
     * 事件,如:new View.OnClickListener()
     *
     * @return
     */
    Class listenerType();
}

点击事件的注解

@Event(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {

    /**
     * 控件id
     *
     * @return
     */
    @IdRes int[] value();
}

长按事件的注解

@Event(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {

    /**
     * 控件id
     *
     * @return
     */
    @IdRes int[] value();
}

注解的使用

@OnClick({R.id.btn1, R.id.btn2})
    public void click(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.btn1:
                Toast.makeText(getApplicationContext(), "按钮1点击了", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn2:
                Toast.makeText(getApplicationContext(), "按钮2点击了", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }

    @OnLongClick({R.id.btn1, R.id.btn2})
    public boolean longClick(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.btn1:
                Toast.makeText(getApplicationContext(), "按钮1长按了", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn2:
                Toast.makeText(getApplicationContext(), "按钮2长按了", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
        return true;
    }

Inject方法

 /**
     * 注入
     *
     * @param target
     */
    public static void inject(Object target) {
        if (target == null) {
            return;
        }
        injectEvent(target);
    }

/**
     * 事件注入
     *
     * @param target 需要注入的组件
     */
    private static void injectEvent(Object target) {
        Class clazz = target.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Class annotationType = annotation.annotationType();
                boolean annotationPresent = annotationType.isAnnotationPresent(Event.class);
                if (!annotationPresent) {
                    continue;
                }
                Event event = annotationType.getAnnotation(Event.class);
                String listenerSetter = event.listenerSetter();
                Class listenerType = event.listenerType();
                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    valueMethod.setAccessible(true);
                    int[] viewIds = (int[]) valueMethod.invoke(annotation);
                    for (int viewId : viewIds) {
                        Method findViewByIdMethod = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewByIdMethod.invoke(target, viewId);
                        if (view == null) {
                            continue;
                        }
                        ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(target, method);
                        Object proxyInstance = Proxy.newProxyInstance(InjectUtil.class.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
                        Method listenerSetterMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        listenerSetterMethod.setAccessible(true);
                        listenerSetterMethod.invoke(view, proxyInstance);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

    static class ListenerInvocationHandler implements InvocationHandler {

        private Object target;

        private Method method;

        public ListenerInvocationHandler(Object target, Method method) {
            this.target = target;
            this.method = method;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return this.method.invoke(target, args);
        }
    }

你可能感兴趣的:(IOC之运行时注入-实现Activity的布局注入+控件注入+事件绑定)