上一篇文章http://blog.csdn.net/crazy__chen/article/details/47425779

和大家一起模仿EventBus的实现机制。和大家一起写出了一个简易的EventBus。

通过这个项目,大家应该对EventBus的实现机理由大致的了解。

建议大家在看这篇文章之前。先读上一篇文章,有助于了解。

本篇文章希望通过对Github上EventBus的源代码。向大家讲些事实上现细节。

网上有非常多讲EventBus架构的文章。我看了以后都认为不大清晰,希望这篇文章能够帮到你。

EventBus源代码地址https://github.com/greenrobot/EventBus

对于整个框架的理解,我们先来看一下文件图

我们希望了解整个框架的每一个文件存在的意义,在这里我将具体介绍大部分主要类的用处和设计的思路。

我们都知道整个框架的入口是EventBus这个类,网上非常多文章都是以这个类为入口

我却先给大家将一些实体类的构建。

我们知道,EventBus解耦的方式事实上是使用了反射。我在调用register()方法的时候,EventBus就扫描调用这种方法的类,将须要反射调用的方法信息记录下来,这些方法就是onEvent(),onEventMainThread(),onEventBackgroundThread()和onEventAsync()。

这些方法被记录以后,假设有人调用post()方法,就会在记录里面查找,然后使用发射去触发这些方法

反射触发方法的方式是调用Method类的invoke()方法,其签名例如以下:

public Object invoke(Object obj, Object... args)

显然要我们须要1,Method对象,订阅者对象obj。还有參数对象args

也就是说记录里面,相应一个订阅方法。起码要有上面三个參数。

OK,依据面向对象的思想,我们最好将这些參数封装成一个详细的类。接下来我们看一张图

这张图说明了EventBus里面实体类的构造结构,外层的类包裹着里面的类。

也就是外面的方框有一个里面方框所代表的类。作为它的属性

我们能够看到PendingPost包括Subscription,Subscription包括SubscriberMethod,SubscriberMethod包括ThreadMode

我如今向大家说明一下。

首先,最里面的是SubscriberMethod类,它的意义就是EventBus扫描到的订阅方法主体

final class SubscriberMethod {/*** 方法本体*/final Method method;/*** 订阅类型*/final ThreadMode threadMode;/*** 參数类型*/final Class<?> eventType;

这个类包括了EventBus扫描到的方法Method,订阅类型ThreadMode。事实上是一个枚举类。它代表订阅方法的类型(是在主线程,子线程,还是在公布线程执行等等)

public enum ThreadMode {  PostThread,    MainThread, BackgroundThread,Async
}

还有订阅方法的參数Class<?> eventType

试想一下,要假设要反射调用这种方法。那么我们如今仅仅满足了第一个条件,就是我们获得了方法对象本身

然后是Subscription。它的意义就是一个订阅。订阅包括订阅者和订阅方法

final class Subscription {/*** 订阅者*/final Object subscriber;/*** 订阅的方法*/final SubscriberMethod subscriberMethod;/*** 优先级*/final int priority;......
}

能够见到,有订阅者。订阅方法对象SubscriberMethod,一个优先级。优先级是用于队列排序的,后面会讲到,优先级越高。当post的时候。者订阅就越先被触发。

这时反射的第二个条件,反射要传入的第一个參数就具备了。

再次封装成PendingPost,这个类的意义是在队列中排队的一个详细运行请求,也就是post以后,就会产生这样一个对象

final class PendingPost {Object event;Subscription subscription;PendingPost next;.......
}

当中Object event,就是我们post(Object obj)方法调用的时候。參数的obj。这就是我们反射须要的最后一个參数。

这样,我们通过构建这几个层次分明的实体类,我们就具备了触发反射的能力。

通过上面的分析我们也知道。我们在register()的时候。目的是获得Subscription对象

我们在post(Object obj)的时候,目的是将传入的obj和对应的Subscription对象一起封装成PendingPost对象

将其放入队列里面排队,队列会不断取出元素。触发反射!

了解上面思路以后,我们開始从EventBus入手。看看最基本的register和post方法,详细是再怎么做到的。

再次之前。我们要看EventBus里面的几个重要属性,这几个属性非常复杂。大家先有个印象

public class EventBus {/** Log tag, apps may override it. */public static String TAG = "Event";static volatile EventBus defaultInstance;private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();/*** key为參数类型,value为參数类型及其父类型,接口类型列表*/private static final Map<Class<?

>, List<Class<?

>>> eventTypesCache = new HashMap<Class<?

>, List<Class<?>>>(); /** * key为參数类型,value为该參数类型的全部订阅(订阅包含,订阅者,订阅方法,优先级) */ private final Map<Class<?

>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; /** * key为订阅者,value为參数列表 */ private final Map<Object, List<Class<?

>>> typesBySubscriber; /** * 能够看做缓存,key參数类型,value是參数 */ private final Map<Class<?>, Object> stickyEvents; ....... }

这么多属性有什么意义?有的是为了功能必须,有的是为了提高性能,比如缓存。

再看源代码过程中我会看到他们的作用。

可是如今我们必须关注的一个属性是subscriptionsByEventType。这个属性我们在上一篇文章也说过它的重要作用。

subscriptionsByEventType的key是參数类型,比如我们又一个onEvent(Info i)方法,我们知道EventBus是通过參数类型找到订阅方法的,比如post(new Info("msg"));

EventBus负责找到所以订阅了Info类型的订阅方法。比如上面说的onEvent(Info i)

那么这就要求我们将Info.class记录下来。作为key,通过这个key,我们就能够找到全部这些方法了。

所以。subscriptionsByEventType的value是一个ArrayList,里面存储了Subscription。

OK,如今让我们来看register()方法

private synchronized void register(Object subscriber, boolean sticky, int priority) {List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());for (SubscriberMethod subscriberMethod : subscriberMethods) {//遍历改订阅者的全部订阅方法subscribe(subscriber, subscriberMethod, sticky, priority);}}

在这种方法里面,调用了subscriberMethodFinder这个类的findSubscriberMethods()方法。依据上面的分析我们也知道。这种方法用于找出订阅者subscriber中的全部订阅方法,而且将它们封装成SubscriberMethod对象返回。

然后调用了subscribe()方法。将SubscriberMethod对象。和订阅者一起,封装成了subscription对象,我们详细看这两个方法。

首先是findSubscriberMethods()

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {String key = subscriberClass.getName();//订阅者名称List<SubscriberMethod> subscriberMethods;//订阅的方法集synchronized (methodCache) {subscriberMethods = methodCache.get(key);}if (subscriberMethods != null) {return subscriberMethods;}subscriberMethods = new ArrayList<SubscriberMethod>();Class<?

> clazz = subscriberClass; /** * 记录订阅的全部方法,防止父类反复订阅在子类中已经订阅的方法 */ HashSet<String> eventTypesFound = new HashSet<String>(); StringBuilder methodKeyBuilder = new StringBuilder(); while (clazz != null) { String name = clazz.getName(); if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) { // Skip system classes, this just degrades performance break; } // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) Method[] methods = clazz.getDeclaredMethods();//找到订阅者的全部方法 for (Method method : methods) { String methodName = method.getName(); if (methodName.startsWith(ON_EVENT_METHOD_NAME)) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {//仅仅找public方法。并且没有static,final等修饰 Class<?>[] parameterTypes = method.getParameterTypes();//方法參数 if (parameterTypes.length == 1) { String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length()); ThreadMode threadMode; if (modifierString.length() == 0) { threadMode = ThreadMode.PostThread; } else if (modifierString.equals("MainThread")) { threadMode = ThreadMode.MainThread; } else if (modifierString.equals("BackgroundThread")) { threadMode = ThreadMode.BackgroundThread; } else if (modifierString.equals("Async")) { threadMode = ThreadMode.Async; } else { if (skipMethodVerificationForClasses.containsKey(clazz)) {//过滤 continue; } else { throw new EventBusException("Illegal onEvent method, check for typos: " + method); } } Class<?> eventType = parameterTypes[0];//參数 methodKeyBuilder.setLength(0); methodKeyBuilder.append(methodName); methodKeyBuilder.append('>').append(eventType.getName()); String methodKey = methodKeyBuilder.toString(); if (eventTypesFound.add(methodKey)) {//假设之前没有加入 // Only add if not already found in a sub class subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType)); } } } else if (!skipMethodVerificationForClasses.containsKey(clazz)) { Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "." + methodName); } } } <pre name="code" class="java"> clazz = clazz.getSuperclass();//查找父类

} if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " + ON_EVENT_METHOD_NAME); } else { synchronized (methodCache) { methodCache.put(key, subscriberMethods); } return subscriberMethods; } }
上面的代码有点复杂,事实上就是扫描订阅者,找到订阅方法。

注意有这样一个

 /*** 记录订阅的全部方法,防止父类反复订阅在子类中已经订阅的方法*/HashSet<String> eventTypesFound = new HashSet<String>();

这个eventTypesFound是记录了整个扫描过程中扫描到的方法签名。为什么要记录呢?

看到这一句

clazz = clazz.getSuperclass();//查找父类

也就是说扫描一个订阅者的时候,假设这个订阅者本身没有订阅方法,可是它的父类有订阅方法,也会被扫描出来

这样的优化就是为了实现继承的作用。

所以扫描父类的时候,要避免反复记录子类中已经扫描到的订阅方法。

另外另一点。就是skipMethodVerificationForClasses,它能够用于过滤不须要扫描的方法,也就是这些订阅不会被记录

这个属性时在创建时传入的对象,我们看SubscriberMethodFinder的构造函数

SubscriberMethodFinder(List<Class<?>> skipMethodVerificationForClassesList) {skipMethodVerificationForClasses = new ConcurrentHashMap<Class<?

>, Class<?

>>(); if (skipMethodVerificationForClassesList != null) { for (Class<?> clazz : skipMethodVerificationForClassesList) { skipMethodVerificationForClasses.put(clazz, clazz); } } }

传入一个名单。名单内的订阅者将会被忽略。

最后,在封装过程中,依据方法签名,构造SubscriberMethod,而且将SubscriberMethod加入到队列里面,最后返回这个队列。

这样findSubscriberMethods()就结束了。

我们接着register()方法里面的

for (SubscriberMethod subscriberMethod : subscriberMethods) {//遍历改订阅者的全部订阅方法subscribe(subscriber, subscriberMethod, sticky, priority);}

这里将方法封装成subscription

// Must be called in synchronized blockprivate void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {Class<?

> eventType = subscriberMethod.eventType; CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);//封装成<span style="font-family: Arial, Helvetica, sans-serif;">newSubscription</span> if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<Subscription>(); subscriptionsByEventType.put(eventType, subscriptions);//假设该參数类型没有队列。新建一个 } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) // subscriberMethod.method.setAccessible(true); int size = subscriptions.size(); for (int i = 0; i <= size; i++) {//依据优先级找到在队列的位置 if (i == size || newSubscription.priority > subscriptions.get(i).priority) { subscriptions.add(i, newSubscription); break; } } List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);//这里是为了删除订阅的须要 if (subscribedEvents == null) { subscribedEvents = new ArrayList<Class<?>>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (sticky) {//是否粘性 if (eventInheritance) { // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }

上面,依据首先封装出一个subscription。然后推断subscriptionsByEventType里面是否由參数类型相应的队列,没有就创建一个

将subscription增加队列。

然后就涉及到一个typesBySibcriber属性,看我凝视

/*** key为订阅者,value为參数类型列表*/private final Map<Object, List<Class<?>>> typesBySubscriber;

也就是说这个属性能够告诉我们,某个订阅者。订阅了哪些參数类型

通过这个记录。我们能够为某个订阅者解除订阅,看EventBus里面的unregister()方法

/** Unregisters the given subscriber from all event classes. */public synchronized void unregister(Object subscriber) {List<Class<?

>> subscribedTypes = typesBySubscriber.get(subscriber);//找到订阅的參数类型 if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unubscribeByEventType(subscriber, eventType);//为该类型解除订阅 } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */private void unubscribeByEventType(Object subscriber, Class<?

> eventType) { List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } }

通过上面的方法。再在subscriptionsByEventType中找到參数类型列表中的subscription队列,再在队列中找到订阅者subscriber,将它们从队列清除

这样就实现了解除订阅。

OK。subscribe()方法的最后,另一个关于sticky的推断,这个我们后面再讲,也就是subscribe()方法先说到这,register()方法也说到这。

下一篇文章。我会继续讲post()方法的实现。

假设对文章内容有不论什么疑惑,欢迎留言。

转载请注明出处。

EventBus框架原理解析(结合源代码)(上)相关推荐

  1. python爬虫基础教程115_Python解析网页源代码中的115网盘链接实例

    本文实例讲述了python解析网页源代码中的115网盘链接的方法.分享给大家供大家参考.具体方法分析如下: 其中的1.txt,是网页http://bbs.pediy.com/showthread.ph ...

  2. python自动源码_谷歌推出Tangent开源库,在Python源代码上做自动微分

    原标题:谷歌推出Tangent开源库,在Python源代码上做自动微分 李林 编译自 Google Research Blog 谷歌今天推出了一个新的开源Python自动微分库:Tangent. 和现 ...

  3. Lakehouse 架构解析与云上实践

    简介:本文整理自 DataFunCon 2021大会上,阿里云数据湖构建云产品研发陈鑫伟的分享,主要介绍了 Lakehouse 的架构解析与云上实践. 作者简介:陈鑫伟(花名熙康),阿里云开源大数据- ...

  4. 一文解析推特上最常见的加密骗局

    推特是加密用户最常用的社交媒体之一,在方便我们及时同步行业最新消息的同时,也成为了不少加密骗局的媒介.这篇文章将为大家解析推特上最常见的加密货币和NFT骗局,帮助大家识别和避开这些骗局. 虚假链接骗局 ...

  5. SQL Server 深入解析索引存储(上)

    SQL Server 深入解析索引存储(上) 原文:SQL Server 深入解析索引存储(上) 标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/堆 概述 最近要分 ...

  6. 解析java源代码_一步步解析java执行内幕

    对于任何一门语言,要想达到精通的水平,研究它的执行原理(或者叫底层机制)不失为一种良好的方式.在本篇文章中,将重点研究java源代码的执行原理,即从程 序员编写JAVA源代码,到最终形成产品,在整个过 ...

  7. 00截断上传绕过_【文件上传与解析】文件上传与解析漏洞总结v1.0

    点击上方"公众号" 可以订阅哦! Hello,各位小伙伴晚上好~ 这里是依旧勤劳写公众号的小编~ 今天本公众号将推出一个新的模块,那就是漏洞知识点总结模块!!!(此处应有掌声~) ...

  8. ViewPager+Fragment切换时无法更新数据问题解析(源代码分享)

    在一个 Android 应用中,我们可以使用 FragmentPageAdapter 来处理多 Fragment 页面的横向滑动.但是当 Fragment 对应的数据集发生改变时,我们都希望能够通过调 ...

  9. Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)

    在之前一篇博文中<< Android中View绘制流程以及invalidate()等相关方法分析>> ,简单的阐述 了 Android View 绘制流程的三个步骤,即: 1. ...

  10. jQuery工作原理解析以及源代码示例

    jQuery的开篇声明里有一段非常重要的话:jQuery是为了改变javascript的编码方式而设计的. 从这段话可以看出jQuery本身并不是UI组件库或其他的一般AJAX类库. jQuery改变 ...

最新文章

  1. Linux内核Makefile
  2. ajax传递数组后台接收不到值的问题
  3. 【PHP】 debian apt 安装PHP7.1 安装composer
  4. POJ 3349 Snowflake Snow Snowflakes
  5. angularAMD快速入门
  6. java for update 无效_java.sql.BatchUpdateException:调用中的无效参数
  7. 100 行 Python 代码实现人体肤色检测
  8. poj 3032 模拟
  9. UVA11577 Letter Frequency【文本】
  10. 1.阿里云短信验证操作步骤
  11. 部署VC2008应用程序时不能运行解决办法
  12. IT笔试题收集,免费下载
  13. Android studio 打包 uni App 修改apk名称,app名称及图标
  14. Restful API设计规范及实战
  15. Ubantu基础指令大集合
  16. 已解决The method is not allowed for the requested URL.
  17. VIGA--病毒基因组注释
  18. 生产计划排程APS系统整体结构
  19. 10-253 B2-1查找订单数最多的员工信息
  20. python采用强制缩进的方式是代码具有极佳的可读性_python入门

热门文章

  1. java.util.ConcurrentModificationException的解决办法
  2. 30天敏捷结果(14):要事第一
  3. Failed to load selinux policy, Freezing | CentOS | Redhat | RHEL
  4. exfat linux 驱动_「正点原子Linux连载」第二章Ubuntu系统入门(2)
  5. Android下基于UVC的UsbCam的开发
  6. ffmpeg代码实现自定义decoder
  7. 51nod 1133 不重叠的线段
  8. java8 新特性_Java8新特性系列之-日期处理详解(二)
  9. 每日一题 PAT 甲级1001 格式化输出
  10. ImportError: cannot import name ‘abs‘