了解一下xutils和ButterKnife流程

2017-12-22  本文已影响0人  Noblel

xutils源码阅读

@Override
public void inject(Activity activity) {
    //获取Activity的ContentView的注解
    Class<?> handlerType = activity.getClass();
    try {
        ContentView contentView = findContentView(handlerType);
        if (contentView != null) {
            //拿到注解中value值
            int viewId = contentView.value();
            if (viewId > 0) {
                //利用反射执行setContentView方法
                Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                setContentViewMethod.invoke(activity, viewId);
            }
        }
    } catch (Throwable ex) {
        LogUtil.e(ex.getMessage(), ex);
    }
    
    injectObject(activity, handlerType, new ViewFinder(activity));
}

//注入属性
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
    try {
        //
        View view = finder.findViewById(viewInject.value(), viewInject.parentId());
        if (view != null) {
            field.setAccessible(true);
            field.set(handler, view);
        } else {
            throw new RuntimeException("Invalid @ViewInject for "
                    + handlerType.getSimpleName() + "." + field.getName());
        }
    } catch (Throwable ex) {
        LogUtil.e(ex.getMessage(), ex);
    }
}

//注入方法
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
    for (Method method : methods) {
        //判断是否是static
        if (Modifier.isStatic(method.getModifiers())
                || !Modifier.isPrivate(method.getModifiers())) {
            continue;
        }

        //检查当前方法是否是event注解的方法
        Event event = method.getAnnotation(Event.class);
        if (event != null) {
            try {
                // id参数
                int[] values = event.value();
                int[] parentIds = event.parentId();
                int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                //循环所有id,生成ViewInfo并添加代理反射
                for (int i = 0; i < values.length; i++) {
                    int value = values[i];
                    if (value > 0) {
                        ViewInfo info = new ViewInfo();
                        info.value = value;
                        info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                        method.setAccessible(true);
                        EventListenerManager.addEventMethod(finder, info, event, handler, method);
                    }
                }
            } catch (Throwable ex) {
                LogUtil.e(ex.getMessage(), ex);
            }
        }
    }
}

public static void addEventMethod(
        //根据页面或view holder生成的ViewFinder
        ViewFinder finder,
        //根据当前注解ID生成的ViewInfo
        ViewInfo info,
        //注解对象
        Event event,
        //页面或view holder对象
        Object handler,
        //当前注解方法
        Method method) {
    try {
        View view = finder.findViewByInfo(info);

        if (view != null) {
            // 注解中定义的接口,比如Event注解默认的接口为View.OnClickListener
            Class<?> listenerType = event.type();
            // 默认为空,注解接口对应的Set方法,比如setOnClickListener方法
            String listenerSetter = event.setter();
            if (TextUtils.isEmpty(listenerSetter)) {
                listenerSetter = "set" + listenerType.getSimpleName();
            }

            String methodName = event.method();

            boolean addNewMethod = false;
            /*
                根据View的ID和当前的接口类型获取已经缓存的接口实例对象,
                比如根据View.id和View.OnClickListener.class两个键获取这个View的OnClickListener对象
             */
            //listenerCache是自定义的双键的Map
            Object listener = listenerCache.get(info, listenerType);
            DynamicHandler dynamicHandler = null;
            /*
                如果接口实例对象不为空
                获取接口对象对应的动态代理对象
                如果动态代理对象的handler和当前handler相同
                则为动态代理对象添加代理方法
             */
            if (listener != null) {
                dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);
                addNewMethod = handler.equals(dynamicHandler.getHandler());
                if (addNewMethod) {
                    dynamicHandler.addMethod(methodName, method);
                }
            }

            // 如果还没有注册此代理
            if (!addNewMethod) {

                dynamicHandler = new DynamicHandler(handler);

                dynamicHandler.addMethod(methodName, method);

                // 生成的代理对象实例,比如View.OnClickListener的实例对象
                listener = Proxy.newProxyInstance(
                        listenerType.getClassLoader(),
                        new Class<?>[]{listenerType},
                        dynamicHandler);

                listenerCache.put(info, listenerType, listener);
            }

            Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
            setEventListenerMethod.invoke(view, listener);
        }
    } catch (Throwable ex) {
        LogUtil.e(ex.getMessage(), ex);
    }
}

所以看了源码就了解到这些内容,XUtils是中国人写的,里面好多注释是中文的相信大家都能看得懂,如果遇到看不懂的地方大概是用了设计模式,比如动态代理。再去了解一下设计模式,耐心看都能看的懂得,不要着急。

ButterKnife 源码阅读

源码链接:ButterKnifeProcessor.java

//编译时注解
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

ButterKnife:

我们在build.gradle中写了这两行代码,而且我们看得到注解是@Retention(CLASS),所以要去找生成Class文件

compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

我们可以看到build-》intermediates-》classes-》debug-》com.xxxx.xxx找到你的使用注解的类,看到会有一个相同前缀的比如MainActivity_ViewBinding.class。大概就明白了一些事情。
原来是ButterKnife帮我们生成了一个class文件,这个class文件会在MainActivity执行ButterKnife.bind(this)的时候执行

ButterKnife

/**
 * 从Activity中bind会来到此方法
 */ 
public static Unbinder bind(@NonNull Activity target) {
    //获取DecorView
    View sourceView = target.getWindow().getDecorView();
    //执行方法2
    return createBinding(target, sourceView);
}

/**
 * 方法2
 */ 
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //这里的constructor实际上就是实现Unbinder的Activity的构造方法,执行方法3
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    
    if (constructor == null) {
      return Unbinder.EMPTY;
    }
    
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      //直接调用了构造方法把target和source传进去,然后执行Butterknife生成的辅助class中的构造方法,调用方法4
      return constructor.newInstance(target, source);
    } 
    ....
}
  
/**
 * 方法3
 */   
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //从缓存中获取构造方法
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    //如果class的名字是以android.或java.开头的直接返回null
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //获取Activity的class对象比如 MainActivity_ViewBinding
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //得到构造器对象会返回回去,回到方法2
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      //如果没有找到就找父类,如果我们写的Activity是extends BaseActivity,BaseActivity中注入了但是子Activity没有,那就会走到这里。
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    //放入缓存,这里的缓存使用的是有序的HashMap-》LinkedHashMap
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

MainActivity_ViewBinding.class

/**
 * 方法4
 */   
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
}

/**
 * 方法5
 */  
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;
    //获取我们绑定id的View,这里也是说明了为啥我们不能private了,如果私有了就不能直接target.title了必须反射获取属性,那就有一个性能问题了。我们看下方法6怎么执行的
    target.title = (TextView)Utils.findRequiredViewAsType(source, 2131099679, "field 'title'", TextView.class);
    View view = Utils.findRequiredView(source, 2131099658, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = (Button)Utils.castView(view, 2131099658, "field 'hello'", Button.class);
    this.view2131099658 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
        public void doClick(View p0) {
            target.sayHello();
        }
    });
    view.setOnLongClickListener(new OnLongClickListener() {
        public boolean onLongClick(View p0) {
            return target.sayGetOffMe();
        }
    });
}

Utils.java

/**
 * 方法6
 */  
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
    //方法7
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
}

/**
 * 方法7
 */ 
public static View findRequiredView(View source, @IdRes int id, String who) {
    //看到这我们就明白了这个框架是如果让我们那么方便了也是调用了findViewById
    View view = source.findViewById(id);
    if (view != null) {
        return view;
    }
    //异常处理....
}

总结

XUtils流程

属性注入:利用反射 获取Annotation-》value-》findViewById-》反射注入属性
事件注入:利用反射获取Annotation-》value-》findViewById-》setOnclickListener-》动态代理反射执行方法

ButterKnife流程

属性注入: ButterKnifeProcessor 生成.java(会再一篇文章研究下这个)--> .class文件-》运行时调用viewBinder.bind(finder, target, source)-》执行辅助类的构造方法-》findViewById得到View赋值给原Activity的View-》执行setText()等操作

事件注入:辅助class中直接执行view.setOnClickListener()

既然知道这个是怎么运行的了,但是我们还有一个疑惑,我们自己写的话怎么生成xxxxx.class呢?我们能不能直接修改MainActivity.class呢?还需要我们进一步研究,学习就是不断思考和反思的过程。
写给自己: 加油~

后面会写一篇怎么生成class文件的学习记录文章。和怎么自己写IOC框架

上一篇下一篇

猜你喜欢

热点阅读