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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值