ButterKnife的实现原理解密之IOC思想

2019-10-13  本文已影响0人  依玲之风

ButterKnife只要是写过Android的相信都不会陌生,它是一款控件注入、事件注如神器可以帮助开发者提高开发效率。
什么是IOC技术呢?说到IOC它的解释其实看上去并没有那么的容易懂,通俗一点讲IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转,想要更详细的要了解IOC的思想请到Spring的原码查看,这里只分享IOC思想的一部分。
那如何使用IOC思想实现Butterknife的控件注入的功能呢?下面将用代码来讲解实际中是如何实现的。
首先得有像Butterknife一样的自定义注解来标记一下哪一个控件是需要注入的,所以得定义一个自定义的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

这个注解是在运行时生效的,同时是使用在成员变量上的,有了这个注解后就是可以写一个注入工具来实现控件的注入了,其实现原理是使用注解+反射来达到注入的效果,通过查找类中的所有成员找到带有自定义的注解拿到该控件的id值,然后反射系统中的findViewByid()的方法来达到注入的效果。

   /**
     *
     * @param targer
     */
    private static void injectFindViewById(Object targer) {
        Class<?> aClass = targer.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if(viewInject != null){
                int viewId = viewInject.value();
                try {
                    Method findViewByIdMethod = aClass.getMethod("findViewById",int.class);
                    View view = (View)findViewByIdMethod.invoke(targer,viewId);
                    field.setAccessible(true);
                    field.set(targer,view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

这个就是使用IOC技术来实现Butterknife的控件注入的功能,Butterknife还一个事件注入的功能这个功能的实现原理和控件注入差不多;接下就看一下事件的注入是如何做到的,这里的只实现一种事件注入的实现分析OnClick()的事件注入只要理解了一种的事件的注入其它的22种事件也是一样的。同样也是要定义一个自定义的注解来标记哪一个方法需要事件的注入。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class,callbackMethoed = "onClick")
public @interface OnClick {
    int[] value() default -1;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
    String listenerSetter();
    Class<?> listenerType();
    String callbackMethoed();
}

这里还需要再定义一个EventBase注解来记录这事件是要设置系统中的哪一个事件监听。有了注解后就是要来实现这个注入的功能了。

/**
    * 事件注入
    * @param targer
    */
   private static void injectEvent(Object targer) {
       Class<?> aClass = targer.getClass();
       Method[] declaredMethods = aClass.getDeclaredMethods();
       for (Method method : declaredMethods) {
           Annotation[] annotations = method.getAnnotations();
           for (Annotation annotation : annotations) {
               Class<?> annotationClass = annotation.annotationType();
               EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
               if(eventBase == null){
                   continue;
               }
               String listenerSetter = eventBase.listenerSetter();
               Class<?> lisetenerType = eventBase.listenerType();
               String callBackMethod = eventBase.callbackMethoed();
               Method valueMethod = null;
               try{
                   valueMethod = annotationClass.getDeclaredMethod("value");
                   int[] viewId = (int[]) valueMethod.invoke(annotation);
                   for (int id : viewId) {
                       Method findViewByid = aClass.getMethod("findViewById",int.class);
                       View view = (View) findViewByid.invoke(targer,id);
                       if(view == null){
                           continue;
                       }
                       ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(targer,method);
                       Object proxy =  Proxy.newProxyInstance(lisetenerType.getClassLoader(),new Class[]{lisetenerType},invocationHandler);
                       Method onClickMethod = view.getClass().getMethod(listenerSetter,lisetenerType);
                       onClickMethod.invoke(view,proxy);
                   }
               }catch (Exception e){
                   e.printStackTrace();
               }
           }
       }
   }

其实现原理和控件注入是一样的都是通过反射系统中的方法来实现的,只是在事件的注入中要使用到动态代理才能把事件的回调到相应的方法上。

public class ListenerInvocationHandler implements InvocationHandler {

    private Object activity;
    private Method activityMethod;

    public ListenerInvocationHandler(Object activity, Method activityMethod) {
        this.activity = activity;
        this.activityMethod = activityMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return activityMethod.invoke(activity,args);
    }
}

以上就是使用IOC的思想来实现ButterKnife的控件的注入和事件的注入,从上述可以看到所有的注入都是使用反射来实现的从性能上来看其实并不太好,下面分析ButterKnife的实现过程。为了解决这个性能问题ButterKnife对IOC做了优化,从运行时改成了编译时来完成所有的注入功能。
要从编译时完成控件的注入和事件的注入那就使用注解处理器来完成这个事情。代码的实现下次再分享了。
项目代码链接:https://github.com/WayneQi/WayneDemo

上一篇 下一篇

猜你喜欢

热点阅读