美文网首页
手写XUtils IOC注入式框架

手写XUtils IOC注入式框架

作者: Laughing_G | 来源:发表于2019-10-04 15:27 被阅读0次

    一、XUtils的介绍和使用

    https://blog.csdn.net/u013472738/article/details/73253103

    二、IOC定义

    官方定义:控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入。通过控制反转,对象在被创建的时候,有一个调用系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。
    很俗的例子:王宝强将公司交给经纪人老宋管理,再将工作安排交给老宋管理,甚至将自己的老婆交给老宋管理,最后老宋控制了王宝强的一切,这就叫做控制反转

    XUtils的设计模式就是基于IOC注入式框架来编写的,这篇文章主要介绍ViewUtils的设计思想。

    三、手写XUtils的ViewUtils

    使用过XUtils的同学们会知道,它省去了setContentView和findViewById的操作,都是通过一个注解注入工具类实现,所以很显然用到了注解类,我们先来看它如何省略掉setContentView的操作的:

    步骤一、先定义一个ContentView的注解:

    //程序运行时,注解也能生效
    @Retention(RetentionPolicy.RUNTIME)
    //注解定义的位置,TYPE是定义在类上面
    @Target(ElementType.TYPE)
    public @interface ContentView {
        int value();
    }
    

    步骤二、MainActivity类指定ContentView的注解:

    image.png

    步骤三、定义注解管理类InjectUtils

    private static void injectLayout(Object context) {
            int layoutId = 0;
            Class<?> clazz = context.getClass();
            //反射找到class类中使用的ContentView注解
            ContentView contentView = clazz.getAnnotation(ContentView.class);
            if (null != contentView) {
                //拿到注解的value(R.layout.activity_main)
                layoutId = contentView.value();
                try {
                    //反射找到setContentView的方法
                    Method method = context.getClass().getMethod("setContentView", int.class);
                    //最后,反射执行setContentView
                    method.invoke(context, layoutId);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    

    findViewById的注解注入方法与setContentView的类似,这里就不过多描述。

    延伸的问题,如何定义Click点击事件的注解呢?我们了解过的Android 的View的点击事件可能有多个事件类型(短按、长按、dialog点击、viewHolder点击)等等,所以如何区分这些点击事件,并且代码看起来不那么冗余呢?这里用到了注解之上再使用注解的方法,并结合动态代理的设计模式去调用真正的onClick事件。

    四、如何处理多事件的注入

    首先理解事件三要素:
    举个例子:

    textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            
        }
    }); 
    

    1.setOnClickListener: 事件订阅者
    2.onClickListener: 事件观察者
    3.onClick:事件被观察者

    依据这个事件三要素的思想,我们定义一个注解之上的注解EventBase:

    @Retention(RetentionPolicy.RUNTIME)
    
    //该注解在另一个注解上使用
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface EventBase {
    
        //事件三要素之一的:订阅者
        String listenerSetter();
    
        //事件三要素之二:事件源
        Class<?> listenerType();
    
        //事件三要素之三:事件类型(长按或者短按)
        String callbackMethod();
    }
    

    在OnClick注解类结合EventBase注解:

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

    同理,OnLongClick注解这样定义:

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

    好了,接下来重点是怎么去处理MainActivity中被OnClick定义的注解的方法,然后包装内部的点击事件的处理,我们来看injectClick这个方法:

    private static void injectClick(Object context) {
            Class<?> aClass = context.getClass();
            //先拿到MainActivity的所有方法
            Method[] methods = aClass.getDeclaredMethods();
            for (Method method : methods) {
                //找到被注解的方法
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation : annotations) {
                    //annotation 就是类似:OnClick这个注解
                    Class<? extends Annotation> annotationType = annotation.annotationType();
                    //找到注解上面的注解(EventBase)
                    EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                    if (eventBase == null) {
                        continue;
                    }
                    //获取事件三要素
                    String listenerSetter = eventBase.listenerSetter();
                    Class<?> listenerType = eventBase.listenerType();
                    String callbackMethod = eventBase.callbackMethod();
    
                    //事件三要素有了,现在还差调用这个事件的主角(textView)
                    Method valueMethod = null;
                    try {
                        //怎么取拿到view的对象呢?用反射,拿到value,然后再通过viewId反射拿对象
                        valueMethod = annotationType.getDeclaredMethod("vaule");
    
                        int[] viewId = (int[]) valueMethod.invoke(annotation);
                        for (int id : viewId) {
                            Method findViewById = aClass.getMethod("findViewById", int.class);
                            View view = (View) findViewById.invoke(context, id);
                            if (view == null) continue;
    
                            //这里拿到的是MainActivity中被@OnClick注解的方法
                            Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
    
                            //这里用到了动态代理,在使用动态代理之前一定要搞清楚你代理的是哪个对象的哪个方法
                            //现在我们需要代理MainActivity对象的click方法,mothod是MainActivity内部的方法
                            ListenerInvocationHandler listenerInvocationHandler =
                                    new ListenerInvocationHandler(context, method);
    
                            Object proxyInstance =
                                    Proxy.newProxyInstance(listenerType.getClassLoader(),
                                            new Class[]{listenerType},
                                            listenerInvocationHandler);
                            onClickMethod.invoke(view, proxyInstance);
    
                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    写在结尾:虽然XUtils这个开源框架已经逐渐退出历史舞台,但是它内部的设计思想始终值得我们学习和借鉴,学完了这篇文章,我们要知道XUtils内部的ViewUtils凭什么能用最少量的代码去实现23中事件注入?如果面试问到了该怎么回答呢:
    自己总结的答案:XUtils事件注入的设计方式是注解之上再次封装了一层注解EventBase,最为顶层注解,你要知道任何一个事件都逃不开三要素:1.事件订阅者(setOnClickListener);2.事件被观察者(也就是事件源onClickListener);3.事件观察者(也就是消费者onClick)。所以依据这三点取定义顶层注解的方法,在onClick和onLongClick注解就引用这个顶层注解,表明它的作用域,所以我们后续再注解方法的时候,就可以通过层层反射来拿到我们想要拿到的东西,再通过一次动态代理,去实现Activity或者其他类中被注解的方法。回答结束!

    Demo地址:
    https://github.com/cWX411904/IOC_XUtils

    相关文章

      网友评论

          本文标题:手写XUtils IOC注入式框架

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