手写XUtils IOC注入式框架

一、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 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注入式框架)