美文网首页
IOC注入框架设计

IOC注入框架设计

作者: Coder_Sven | 来源:发表于2020-01-02 15:42 被阅读0次

    什么是IOC注入框架

    IOC-控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。这段百度对IOC框架的解释,对于Java开发者来讲最著名的IOC框架莫过于Spring,而在我们的Android开发中,IOC的使用更为常见,比如大家经常使用的XUtil、butterKnife、EventBus、dagger、dagger2、otto等等,这些第三方库几乎都使用了IOC思想,比如使用ButterKnife对于view的注入减少了大量篇幅的findViewById操作,而注解注入的方式也显得更加优雅。

    IOC思想

    IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转

    1577951887382.png

    如何实现IOC

    看看示例代码

    @ContentView(R.layout.activity_main)
    public class MainActivity extends BaseActivity {
    
        @ViewInject(R.id.app_text)
        private Button textView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    //        textView.setOnClickListener(new View.OnClickListener() {
    //            @Override
    //            public void onClick(View v) {
    //                Toast.makeText(MainActivity.this,"点击了",Toast.LENGTH_SHORT).show();
    //            }
    //        });
        }
    
        @OnClick({R.id.app_text,R.id.app_text1})
        public boolean click(View view){
            Toast.makeText(this,"---->"+textView,Toast.LENGTH_SHORT).show();
            return false;
        }
    
        @OnLongClick({R.id.app_text,R.id.app_text1})
        public boolean longClick(View view){
            Toast.makeText(this, "长按了", Toast.LENGTH_SHORT).show();
            return true;
        }
    }
    
    package com.highgreat.sven.ioc;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class BaseActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            InjectUtils.inject(this);
        }
    }
    
    public class InjectUtils {
    
        public static void inject(Object object){
            injectLayout(object);
            injectView(object);
            injectClick(object);
        }
        ...
    }
    

    布局注入

    @ContentView(R.layout.activity_main)

    需要创建ContentView注解

    package com.highgreat.sven.ioc;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)//注解在运行时执行
    @Target(ElementType.TYPE)//作用在类上面
    public @interface ContentView {
        int value();
    }
    

    Java实现注解的执行逻辑

        /**
         * 布局注入
         * @param object
         */
        private static void injectLayout(Object object) {
            int layoutId = 0;
            Class<?> aClass = object.getClass();
            ContentView annotation = aClass.getAnnotation(ContentView.class);
            if(annotation != null){
                layoutId = annotation.value();
                try {
                    //反射Activity的setContentView方法
                    Method method = aClass.getMethod("setContentView", int.class);
                    method.invoke(object,layoutId);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    

    这段代码首先通过object.getClass()拿到这个Class对象,再通过aClass.getAnnotation(ContentView.class);拿到类上的注解,取得layout的id,然后反射Activity的setcontentView(R.id.layout)方法,这样就实现了布局的注入

    控件注入

    定义注解文件

    package com.highgreat.sven.ioc;
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)//字段
    public @interface ViewInject {
        int value();
    }
    

    java执行逻辑

        /**
         * 控件注入
         * @param object
         */
        private static void injectView(Object object) {
            Class<?> aClass = object.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields) {
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if(viewInject != null){
                    int value = viewInject.value();
                    try {
                        Method method = aClass.getMethod("findViewById", int.class);
                        View view = (View) method.invoke(object,value);
                        field.setAccessible(true);//设置为可访问的权限。即将private修饰的字段变成可公共访问的
                        field.set(object,view);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    这段代码首先通过aClass.getDeclaredFields()拿到类的所有字段属性,再通过ViewInject viewInject = field.getAnnotation(ViewInject.class);拿到字段属性的注解判断是否是需要注入的字段,再通过Method method = aClass.getMethod("findViewById", int.class);View view = (View) method.invoke(object,value);反射Activity类的findViewById方法获取到对应的view,setContentView方法没有返回值,而findViewById则相反,所以我们需要为属性(这里就是一些View)赋值,调用的是field.set(object,view)。

    Android事件监听规律

    事件的注入相比之前的布局和控件注入,难度和复杂度大大提高了。通过对Android中的事件监听代码的观察,我们得出如下三部曲:

    • setListener
    • new Listener
    • doCallback
      就像View的点击事件和长按时间监听那样,首先setListener:View.setOnClickListener(),然后new 一个Listener传入,View.setOnClickListener(new OnClickListener(View v){}),最后执行回调方法:
      onClick(View v){...}

    定义事件监听规律的注解

    package com.highgreat.sven.ioc;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)//作用在其他注解上
    public @interface EventBase {
    
        //  setOnClickListener  订阅
        String listenerSetter();
    
        /**
         * 事件监听的类型
          */
        Class<?> listenerType();
    
        /**
         * 事件处理
         * @return
         */
        String callbackMethod();
    
    }
    
    

    这个注解是放在其他注解之上的,那么这个注解怎么使用呢,就以View的长按事件监听为例:

    package com.highgreat.sven.ioc;
    
    import android.view.View;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @EventBase(listenerType = View.OnLongClickListener.class
            ,listenerSetter =  "setOnLongClickListener",callbackMethod = "onLongClick")
    public @interface OnLongClick {
        int[] value() default -1;
    }
    
    

    在这个注解上面调用了刚才定义的EventBase注解,根据传入的值大家似乎就什么都看明白了吧,没错这里传入了View.OnLongClickListener事件监听三部曲,因为在一个类中可能不止一个控件会设置长按事件监听,所以这里的返回值是数组。

    事件注入逻辑

        /**
         * 事件注入
         * @param object
         */
        private static void injectClick(Object object) {
            Class<?> aClass = object.getClass();
            //得到类的所有方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //得到方法上的所有注解(避免写死)
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation : annotations) {
                    Class<? extends Annotation> annotationType = annotation.annotationType();
                    //创建一个事件类的超类,所有事件都是一个EventBase
                    EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                    //如果没有eventBase,则表示当前方法不是一个处理事件的方法
                    if(eventBase==null) { 
                        continue;
                    }
                    // 用于确定是哪种事件(onClick还是onLongClick)以及由谁来处理
                    //事件监听的类型
                    Class<?> listenerType = eventBase.listenerType();//View.OnClickListener.class
                    //订阅
                    String listenerSetter = eventBase.listenerSetter();//setOnClickListener
                    //事件处理   事件被触发之后,执行的回调方法的名称
                    String callBackMethod=eventBase.callbackMethod();
    
                    try {
                        //反射得到id,再根据id得到对应的View
                        Method valueMethod = annotationType.getDeclaredMethod("value");
                        int[] viewId = (int[])valueMethod.invoke(annotation);
                        for (int id : viewId) {
                            //根据id反射获取对应的view
                            Method findViewById=aClass.getMethod("findViewById",int.class);
                            View view = (View)findViewById.invoke(object, id);
                            if(view==null) {
                                continue;
                            }
                            //得到ID对应的VIEW以后
                            //开始在这个VIEW上执行监听  (使用动态代理)
                            //需要执行activity上的onClick方法
                            ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(object, method);
                            Object instance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, listenerInvocationHandler);
                           //setOnClickListener(new View.OnClickListener());
                            Method onclickMethod = view.getClass().getMethod(listenerSetter,listenerType);
                            onclickMethod.invoke(view,instance);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    使用动态代理的方式将method的callback方法Onlick交回给activity去处理

    package com.highgreat.sven.ioc;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class ListenerInvocationHandler implements InvocationHandler {
    
        private Object activity;
        private  Method activityMethod; //Onclick
    
        public ListenerInvocationHandler(Object activity, Method activityMethod) {
            this.activity = activity;
            this.activityMethod = activityMethod;
        }
    
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在这里调用Activity下面注解了的click方法
            return activityMethod.invoke(activity,args);
        }
    }
    
    

    项目demo地址

    相关文章

      网友评论

          本文标题:IOC注入框架设计

          本文链接:https://www.haomeiwen.com/subject/btkdoctx.html