Ioc注入框架 注入布局 注入控件 动态代理注入事件

来源:互联网 发布:nginx centos yum 编辑:程序博客网 时间:2024/06/02 04:57

IOC控制反转注入框架

很早之前我们用过Xutils框架 里面有通过注解来使用findViewById 之前我们只是使用。

这样的框架我们要自己实现一遍

主要分为三个部分
1. 注入布局 (利用注解)
2. 注入控件
3. 注入事件 (利用动态代理注入事件)

注入布局

定义注入布局时注解

package com.jiang.iocxutil.annotion;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jiang on 2017/6/17. * 注入布局 * * ElementType.TYPE 表示类的注解 * RetentionPolicy.RUNTIME 运行时注解 一般我们编写的都是基于运行时的注解 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentView{    int value();}

在基类中初始化注入

package com.jiang.iocxutil;import android.os.Bundle;import android.os.PersistableBundle;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;/** * Created by jiang on 2017/6/17. */public class BaseActivity extends AppCompatActivity {    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        InjectUtils.inject(this);    }}

注入布局相关代码

  /**     * 注入布局     * @param context     */    public static void injectLayout(Context context) {        Class<?> clazz =  context.getClass();        ContentView contentView = clazz.getAnnotation(ContentView.class);        if (contentView != null) {            int layoutId = contentView.value();            //setContentView            try {                Method method = clazz.getMethod("setContentView", int.class);                method.setAccessible(true);                method.invoke(context, layoutId);            } catch (NoSuchMethodException e) {                e.printStackTrace();            } catch (InvocationTargetException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }    }

在MainActiviy里面编写注入布局

package com.jiang.iocxutil;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.TextView;import android.widget.Toast;import com.jiang.iocxutil.annotion.ContentView;import com.jiang.iocxutil.annotion.OnClick;import com.jiang.iocxutil.annotion.OnLongClick;import com.jiang.iocxutil.annotion.ViewInject;/** * IOC控制反转框架 * */@ContentView(value = R.layout.activity_main)public class MainActivity extends BaseActivity {    private static final String TAG = "MainActivity";//    @Override//    protected void onCreate(Bundle savedInstanceState) {//        super.onCreate(savedInstanceState);//        setContentView(R.layout.activity_main);//    }    @ViewInject(R.id.text_ioc)    TextView textView;    @Override    protected void onResume() {        super.onResume();        Log.d(TAG, "textView ==" + textView.hashCode());//        textView.setOnClickListener(new View.OnClickListener() {//            @Override//            public void onClick(View v) {////            }//        });//        textView.setOnLongClickListener(new View.OnLongClickListener() {//            @Override//            public boolean onLongClick(View v) {//                return false;//            }//        });    }//    @OnClick(R.id.text_ioc)//    public void click(View view){//        toast("测试点击");//    }    @OnLongClick(R.id.text_ioc)    public boolean click(View view){        toast("测试点击");        return false;    }    public void toast(String string) {        Toast.makeText(this, string, Toast.LENGTH_SHORT).show();    }}

这样就可以通过注入布局来代替展示SetContentView()方法

注入控件

定义注解

package com.jiang.iocxutil.annotion;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jiang on 2017/6/17. * 注入控件注解 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {    int value();}

2.利用反射找到findViewById来注入控件

 /**     * 依赖注入控件     * @param context     */    public static void injectView(Context 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 (NoSuchMethodException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }
  1. 在MainActivity中我们使用 测试一下注入控件是否可用
    @ViewInject(R.id.text_ioc)    TextView textView;    @Override    protected void onResume() {        super.onResume();        Log.d(TAG, "textView ==" + textView.hashCode());    }

注入事件(这个比较复杂也是难点)

为了事件的扩展性 我们要拿到事件的三个要素
1.事件的注册
2.事件的类型
3.事件的回调方法

package com.jiang.iocxutil.annotion;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jiang on 2017/6/17. * 注解的注解 事件的三要素 */@Target(ElementType.ANNOTATION_TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface EventBase {    /**     * 监听事件的方法     * @return String     */    String listenerSetter();    /**     * 事件的类型     * @return Class     */    Class<?> listenerType();    /**     * 事件被触发后的回调方法     * @return     */    String callBackMethod();}

我们先写点击事件

package com.jiang.iocxutil.annotion;import android.app.Dialog;import android.view.View;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jiang on 2017/6/17. * 以EventBase做注解 * * 目前需要View.OnClickListener.class * 为了扩展可能还需要Dialog.OnClickListener.class */@EventBase(listenerSetter = "setOnClickListener"        , listenerType = View.OnClickListener.class        , callBackMethod = "onClick")@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnClick {    int[] value();}

然后通过反射和动态代理去执行回调

 /**     * 注入事件     *     * public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。     * public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,     * 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。     * @param context     */    private static void injectEvent(Context context) {        Class<?> clazz = context.getClass();        Method[] methods = clazz.getMethods();        for (Method method: methods) {            /**             * 扩展性             */            Annotation[] anns = method.getAnnotations();            // 循环拿到方法类型注解            for (Annotation ann: anns) {                // 拿到注解的类  先拿到OnlickC注解                Class<? extends Annotation> anntionType = ann.annotationType();                //然后再拿到注解的注解EvenBase                EventBase eventBase = anntionType.getAnnotation(EventBase.class);                if (eventBase == null) {                    continue;                }                //拿到事件的三要素                // 设置事件 拿到 setOnclickListener                String listenerSetter = eventBase.listenerSetter();                // 事件类型 拿到 View.OnClickListener.class                Class<?> listenerType = eventBase.listenerType();                // 回调方法  拿到 callBackMethod onClick                String callBackMethod = eventBase.callBackMethod();                // 下一步 通过反射 给View 设置                // 继续反射拿到 注解里面的id                Map<String, Method> methodMap = new HashMap<>();                // 得到当前callBackMethod 对应  Onclick  method -- clikText                methodMap.put(callBackMethod, method);                try {                    //                    Method declaredMethod = anntionType.getMethod("value");                    // 注解上的方法  找到Id数组                    int[] valuesId = (int[]) declaredMethod.invoke(ann);                    for (int viewId: valuesId) {                        Method findViewById = clazz.getMethod("findViewById", int.class);                        View view = (View) findViewById.invoke(context, viewId);                        //                        if (view == null) {                            continue;                        }                        //上面的事件三个要素全部拿到了  View的Class  setOnClickListener 对应  view的setOnClickListener                        Method setOnClickListener = view.getClass().getMethod(listenerSetter, listenerType);                        ListenerInvocationHandler handler = new ListenerInvocationHandler(context, methodMap);                        // 拿到动态代理                        Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{ listenerType }, handler);                        // 将我们的方法执行                        setOnClickListener.invoke(view, proxyInstance);                    }                } catch (NoSuchMethodException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }    /**     * 如何给其设置一个监听  onClick又不在InjectUtils回调 而是在MainActivity里面回调     * 动态代理  今天的需求是要给按钮设置一个监听 我要执行的一个回调监听不能卸载InjectUtils里面     * 而是想回调 MainActivity     */

动态代理类的设计

package com.jiang.iocxutil;import android.content.Context;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Map;/** * Created by jiang on 2017/6/17. */public class ListenerInvocationHandler implements InvocationHandler {    private Context context;    private Map<String, Method> methodMap;    public ListenerInvocationHandler(Context context, Map<String, Method> methodMap) {        this.context = context;        this.methodMap = methodMap;    }    /**     * 处理代理对象的代理方法     * 我们要代理谁  MainActivity OnClickListener     * 持有一个真正的对象引用就是MainActivity     * @param proxy     * @param method     * @param args     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        String name = method.getName();        Method mtd = methodMap.get(name);        if (mtd == null) {            //不需要代理            return method.invoke(proxy, args);        } else {            //真正的代理方法            return mtd.invoke(context, args);        }    }}

然后再MainActivity里面测试注册事件

 @OnClick(R.id.text_ioc)    public void click(View view){        toast("测试点击");    }

此时我们的注册事件就已经完工。
我们看一下其扩展性

然后写长按点击事件 首先定义注解类似于点击事件

package com.jiang.iocxutil.annotion;import android.view.View;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jiang on 2017/6/17. * 长按事件的注解 */@EventBase(listenerSetter = "setOnLongClickListener"        , listenerType = View.OnLongClickListener.class        , callBackMethod = "onLongClick")@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnLongClick {    int[] value();}

在Manactivity里面 添加

 @OnLongClick(R.id.text_ioc)    public boolean longLlick(View view){        toast("测试点击");        return false;    }

长按点击也就可以成功了

注入工具类 包含注入布局 注入控件 和 注入事件

package com.jiang.iocxutil;import android.content.Context;import android.view.View;import com.jiang.iocxutil.annotion.ContentView;import com.jiang.iocxutil.annotion.EventBase;import com.jiang.iocxutil.annotion.ViewInject;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;/** * Created by jiang on 2017/6/17. * 依赖注入工具类、 * 分别为注入布局 * 注入控件 * 注入事件 */public class InjectUtils {    /**     * 初始化注入     * @param context     */    public static void inject(Context context) {        injectLayout(context);        injectView(context);        injectEvent(context);    }    /**     * 依赖注入控件     * @param context     */    public static void injectView(Context 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 (NoSuchMethodException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }    /**     * 注入布局     * @param context     */    public static void injectLayout(Context context) {        Class<?> clazz =  context.getClass();        ContentView contentView = clazz.getAnnotation(ContentView.class);        if (contentView != null) {            int layoutId = contentView.value();            //setContentView            try {                Method method = clazz.getMethod("setContentView", int.class);                method.setAccessible(true);                method.invoke(context, layoutId);            } catch (NoSuchMethodException e) {                e.printStackTrace();            } catch (InvocationTargetException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }    }    /**     * 注入事件     *     * public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。     * public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,     * 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。     * @param context     */    private static void injectEvent(Context context) {        Class<?> clazz = context.getClass();        Method[] methods = clazz.getMethods();        for (Method method: methods) {            /**             * 扩展性             */            Annotation[] anns = method.getAnnotations();            // 循环拿到方法类型注解            for (Annotation ann: anns) {                // 拿到注解的类  先拿到OnlickC注解                Class<? extends Annotation> anntionType = ann.annotationType();                //然后再拿到注解的注解EvenBase                EventBase eventBase = anntionType.getAnnotation(EventBase.class);                if (eventBase == null) {                    continue;                }                //拿到事件的三要素                // 设置事件 拿到 setOnclickListener                String listenerSetter = eventBase.listenerSetter();                // 事件类型 拿到 View.OnClickListener.class                Class<?> listenerType = eventBase.listenerType();                // 回调方法  拿到 callBackMethod onClick                String callBackMethod = eventBase.callBackMethod();                // 下一步 通过反射 给View 设置                // 继续反射拿到 注解里面的id                Map<String, Method> methodMap = new HashMap<>();                // 得到当前callBackMethod 对应  Onclick  method -- clikText                methodMap.put(callBackMethod, method);                try {                    //                    Method declaredMethod = anntionType.getMethod("value");                    // 注解上的方法  找到Id数组                    int[] valuesId = (int[]) declaredMethod.invoke(ann);                    for (int viewId: valuesId) {                        Method findViewById = clazz.getMethod("findViewById", int.class);                        View view = (View) findViewById.invoke(context, viewId);                        //                        if (view == null) {                            continue;                        }                        //上面的事件三个要素全部拿到了  View的Class  setOnClickListener 对应  view的setOnClickListener                        Method setOnClickListener = view.getClass().getMethod(listenerSetter, listenerType);                        ListenerInvocationHandler handler = new ListenerInvocationHandler(context, methodMap);                        // 拿到动态代理                        Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{ listenerType }, handler);                        // 将我们的方法执行                        setOnClickListener.invoke(view, proxyInstance);                    }                } catch (NoSuchMethodException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }    /**     * 如何给其设置一个监听  onClick又不在InjectUtils回调 而是在MainActivity里面回调     * 动态代理  今天的需求是要给按钮设置一个监听 我要执行的一个回调监听不能卸载InjectUtils里面     * 而是想回调 MainActivity     */}

匆匆编写 细节还需大家验证 有疑问
邮箱 zhangdanjiang_java@163.com
GitBub地址 https://github.com/JiangGeJavaAndroid/IocXutil

原创粉丝点击