上一篇文章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 block
private 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. 企业私有源代码上传github致入侵之大疆案判决了

    事件简单回顾: 1.2017年8月28日,大疆宣布“大疆威胁识别奖励计划”,最高3万美元: 2.然而在此之前,大疆农业事业部某员工将企业私有源代码上传到了github: 3.就职于大疆竞对公司Depa ...

  2. WordPress搭建教程---购买域名+购买VPS主机+域名DNS解析+网站环境+上传网站程序

    WordPress搭建教程 购买域名---NameSilo 购买VPS主机---Vultr 域名DNS解析 网站环境 上传网站程序 参考文章: 1. WordPress搭建教程 https://zhu ...

  3. 解析CentOS 8上的Xrdp服务器安装

    解析CentOS 8上的Xrdp服务器安装   Linux系统技术交流QQ群(915246)验证问题答案:刘遄 导读 Xrdp 是 Microsoft 远程桌面协议 (RDP) 的开源实现,允许您以图 ...

  4. [Architect] Abp 框架原理解析(2) EventBus

    本节目录 原理介绍 Abp源码分析 代码实现 原理介绍 事件总线大致原理: (1)       在事件总线内部维护着一个事件与事件处理程序相映射的字典. (2)       利用反射,事件总线会将实现 ...

  5. Universal-Image-Loader完全解析--从源代码分析Universal-Image-Loader中的线程池

    一般来讲一个网络访问就需要App创建一个线程来执行,但是这也导致了当网络访问比较多的情况下,线程的数目可能积聚增多,虽然Android系统理论上说可以创建无数个线程,但是某一时间段,线程数的急剧增加可 ...

  6. 机器学习算法实现解析——word2vec源代码解析

    在阅读本文之前,建议首先阅读"简单易学的机器学习算法--word2vec的算法原理"(眼下还没公布).掌握例如以下的几个概念: 什么是统计语言模型 神经概率语言模型的网络结构 CB ...

  7. EventBus原理解析

    前言 EventBus的核心思想是观察者模式 (生产/消费者编程模型) . SpringBoot+EventBus使用教程(一) SpringBoot+EventBus使用教程(二) 通过前面的文章我 ...

  8. EventBus完全解析--组件/线程间通信利器

    github地址:https://github.com/greenrobot/EventBus 1, Android EventBus实战, 没听过你就out了 2,  Android EventBu ...

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

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

随机推荐

  1. Hadoop2.x 关于日志文件位置

    查看日志是发现Hadoop问题和解决Hadoop问题的第一步. 开始我不知道该去哪找日志,后来我发现在我启动节点的时候,有打印信息以及明确告诉了日志写在哪. [root@master hadoop]# ...

  2. sqlmap 实战漏洞平台dvwa进行密码破解

    2016-05-24 (1)实验的具体的环境极其思路 首先我们要检测我们的漏洞平台是否有sql注入 ,进行简单的测试发现在用户userid 上存在注入的漏洞 使用抓包工具对其cookie 进行获取如下 ...

  3. worldcount

    码云项目地址 PSP2.1 PSP阶段 预估耗时 (分钟) 实际耗时 (分钟) Planning 计划 20 20 Estimate 估计这个任务需要多少时间 60 70 Development 开发 ...

  4. vue之computed和watch

    计算属性 computed 侦听器or观察者 watch 一直以来对computed和watch一知半解,用的时候就迷迷糊糊的,今天仔细看了看文档,突然茅塞顿开,原来就是这么简单啊: computed ...

  5. pandas 4 处理缺失数据nan

    from __future__ import print_function import pandas as pd import numpy as np np.random.seed(1) dates ...

  6. Android 之 Eclipse没法生成R文件

    这几天被Eclipse整哭了.也怪自己手贱把appcompat_v7给删了. Eclipse创建project假设是兼容4.0下面,会多生成一个projectappcompat_v7,例如以下图: 这 ...

  7. Linux下PHP开启Oracle支持(oci8)

    使用php的常见问题是:编译php时忘记加入某扩展,后来想加入扩展,可是由于安装php后又装了一些东西如PEAR等,不想删除文件夹重装,那么此时就须要自己又一次添加某模块支持了,Linux操作系统下能 ...

  8. 2.安装 CLI和CLI的工作原理

    转自:https://www.gruntjs.net/getting-started 还在使用 Grunt 0.3 版本吗?请查看 Grunt 0.3 注意事项 在继续学习前,你需要先将Grunt命令 ...

  9. Map, filter and reduce

    To add up all the numbers in a list, you can use a loop like this: Total is initialized to 0. Each t ...

  10. [学习笔记]HTTP协议

    转自:www.cnblogs.com/li0803/archive/2008/11/03/1324746.html Author :Jeffrey 引言 HTTP是一个属于应用层的面向对象的协议,由于 ...