xUtils3源码分析(二)(事件的绑定)

xUtils3源码分析(二)(事件的绑定)
文章图片

本篇是xUtils3源码解析的第二篇,主要分析xUtils3的事件绑定机制,上一篇主要分析了view的绑定机制,感兴趣的同学可以阅读:
xUtils3源码解析(一):View的绑定
另外阅读本文需要动态代理的基本知识,请参阅
亦山: Java动态代理机制详解 个人认为讲的较为简单清晰,但鉴于本人对动态代理并不精通,因此不做说明,请读者见谅。
下面还是通过xUtils3的项目例子作为解析,事件绑定的注解是通过Event这个注解类:
xUtils3源码分析(二)(事件的绑定)
文章图片

该注解作者已经做出了比较详细的说明,我们简单补充下:
- @Target(ElementType.METHOD)说明该注解只能用于方法上;
- @Retention(RetentionPolicy.RUNTIME)说明注解保留到运行时;
- int [] value()说明控件可以声明多个;
- int [] parentId()说明需要绑定事件的控件可以在一个父布局中查找;
- Class type()就是需要的事件类,默认是click事件;
- String setter()就是设置事件的方法,比如setOnClickListener;
- String method()如果接口有多个方法,那么需要指定调用哪个方法;
之后我们使用Event注解写个简单的例子,比如绑定一个按钮,弹出Toast提示:
xUtils3源码分析(二)(事件的绑定)
文章图片

首先还是调用x.view().inject(this); ,之后就是将Event注解绑定在方法上,我们还是看看x.view().inject(this); 做了什么:
xUtils3源码分析(二)(事件的绑定)
文章图片

ViewInjectorImpl实现了ViewInjector接口,该接口上篇做过介绍,这里不再说明。
其实该方法首先是判断了有没有ContentView注解,如果有的话会通过
xUtils3源码分析(二)(事件的绑定)
文章图片

来给Activity设置布局,最后调用

injectObject(activity, handlerType, new ViewFinder(activity));

该方法的前半部分主要是用于View的绑定,上篇已经解释过,下半部分主要是用于事件的绑定,也是我们今天分析的重点:
xUtils3源码分析(二)(事件的绑定)
文章图片

简单起见,只保留了下半部分的代码。
首先我们看到第一句:
Method[] methods = handlerType.getDeclaredMethods();

hanlderType其实就是Activityclass,主要是获取了它所声明的所有方法,目的主要是获取添加了Event注解的方法,之后进入循环,再接着看:
xUtils3源码分析(二)(事件的绑定)
文章图片

如果方法是静态的或不是private类型的,那么不对方法进行检查,然后对方法的注解进行检查:
xUtils3源码分析(二)(事件的绑定)
文章图片

如果Event注解存在,则获取到控件的id和父控件id两个数组:
xUtils3源码分析(二)(事件的绑定)
文章图片

得到数组长度:
xUtils3源码分析(二)(事件的绑定)
文章图片

可以看到父id不写的话长度自然是0,再看:
xUtils3源码分析(二)(事件的绑定)
文章图片

这里可以看到只有填写了控件的id才是能够绑定的,并且对控件id和父控件id进行配对,匹配规则是这样的:
比如控件id1,2,
父控件id4,5,6
那么只匹配前2对,
如果控件id1,2,
父控件id4
那么其实只匹配第1对,也就是匹配两者少的那组,但是没有父id的时候,就是0来代替了。
子控件id和父控件id组成了ViewInfo对象,该对象主要是用来封装这一对id的。
最后一句代码是核心:
xUtils3源码分析(二)(事件的绑定)
文章图片

该方法在内部完成了事件的绑定,让我们看看这个过程:
public static void addEventMethod( //根据页面或view holder生成的ViewFinder ViewFinder finder, //根据当前注解ID生成的ViewInfo ViewInfo info, //注解对象 Event event, //页面或view holder对象 MainActivity Object handler, //当前注解方法 当前是我们定义的test方法 Method method) { try { View view = finder.findViewByInfo(info); //获取需要绑定事件的view控件 当前是 btn_test1按钮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对象 */ 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); //"" 和 "test" } }// 如果还没有注册此代理 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); //然后放入缓存 }/**获取view控件需要绑定的方法,这里根据setOnClickListener方法名,和View.OnClickListener.class 获取setOnClickListener的Method对象,然后通过invoke方法,将View.OnClickListener的代理实例对象 设置给view控件,这样就完成了view事件的绑定,这里我们为啥需要View.OnClickListener的代理实例对象呢? 回想下我们手动调用的写法: view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {} }); 我们需要一个View.OnClickListener的实体类设置给view对象的,不能将接口直接设置给view,所以需要这个 代理实例对象listener,这也就是动态代理的意义所在,希望大家理解。 **/ Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); setEventListenerMethod.invoke(view, listener); } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } }

以上就是绑定事件的过程,还涉及到两个对象:
一个是缓存代理对象的DoubleKeyValueMap的实例类listenerCache,主要用于存放缓存的代理对象,用ConcurrentHashMap作为容器,保证并发安全,有兴趣的同学可以分析下该类,代码较清晰,这里不再分析。
一个是DynamicHandler的实现类dynamicHandler,该类的invoke方法主要用于触发真正的绑定方法,这里我们是test方法,这里我们看下是如何调用的:
public static class DynamicHandler implements InvocationHandler { // 存放代理对象,比如Fragment或view holder,采用弱引用,避免内存泄漏 private WeakReference handlerRef; // 存放代理方法 private final HashMap methodMap = new HashMap(1); private static long lastClickTime = 0; public DynamicHandler(Object handler) { this.handlerRef = new WeakReference(handler); }public void addMethod(String name, Method method) { methodMap.put(name, method); }public Object getHandler() { return handlerRef.get(); }/** *在调用onClick方法时,会调用此方法,完成真正的test方法调用 * @param proxy 代理对象 * @param method 这里是onClick方法 * @param args onClick方法的参数 View * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object handler = handlerRef.get(); if (handler != null) {String eventMethod = method.getName(); //得到onClick方法的名字 if ("toString".equals(eventMethod)) {//如果是toString方法直接返回本类名 return DynamicHandler.class.getSimpleName(); }//以"onClick"作为key,获取绑定的方法,这里自然获取不到,因为Key是"" method = methodMap.get(eventMethod); if (method == null && methodMap.size() == 1) {//这里做下判断 for (Map.Entry entry : methodMap.entrySet()) { if (TextUtils.isEmpty(entry.getKey())) {//因为key是"",所以满足条件 method = entry.getValue(); //这里就是获取""key对应的方法,也就是test方法了 } break; } }if (method != null) { /**这里AVOID_QUICK_EVENT_SET 是个HashSet * AVOID_QUICK_EVENT_SET.add("onClick"); * AVOID_QUICK_EVENT_SET.add("onItemClick"); * eventMethod 的名称是onClick,因此是VOID_QUICK_EVENT_SET * 所包含的 */ if (AVOID_QUICK_EVENT_SET.contains(eventMethod)) { long timeSpan = System.currentTimeMillis() - lastClickTime; if (timeSpan < QUICK_EVENT_TIME_SPAN) {//防止过快点击 LogUtil.d("onClick cancelled: " + timeSpan); return null; } lastClickTime = System.currentTimeMillis(); }try { //最后这里利用test方法反射直接调用,这样就完成了test的方法的调用 return method.invoke(handler, args); } catch (Throwable ex) { throw new RuntimeException("invoke method error:" + handler.getClass().getName() + "#" + method.getName(), ex); } } else { LogUtil.w("method not impl: " + eventMethod + "(" + handler.getClass().getSimpleName() + ")"); } } return null; } }
【xUtils3源码分析(二)(事件的绑定)】以上就是事件绑定的相关分析,通过阅读本模块源码可以学习的知识点为:
注解,动态代理和反射。
接下来将展开其他模块的分析,敬请期待…

    推荐阅读