EventBus框架原理解析(结合源代码)(上)
上一篇文章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框架原理解析(结合源代码)(上)的更多相关文章
- 企业私有源代码上传github致入侵之大疆案判决了
事件简单回顾: 1.2017年8月28日,大疆宣布“大疆威胁识别奖励计划”,最高3万美元: 2.然而在此之前,大疆农业事业部某员工将企业私有源代码上传到了github: 3.就职于大疆竞对公司Depa ...
- WordPress搭建教程---购买域名+购买VPS主机+域名DNS解析+网站环境+上传网站程序
WordPress搭建教程 购买域名---NameSilo 购买VPS主机---Vultr 域名DNS解析 网站环境 上传网站程序 参考文章: 1. WordPress搭建教程 https://zhu ...
- 解析CentOS 8上的Xrdp服务器安装
解析CentOS 8上的Xrdp服务器安装 Linux系统技术交流QQ群(915246)验证问题答案:刘遄 导读 Xrdp 是 Microsoft 远程桌面协议 (RDP) 的开源实现,允许您以图 ...
- [Architect] Abp 框架原理解析(2) EventBus
本节目录 原理介绍 Abp源码分析 代码实现 原理介绍 事件总线大致原理: (1) 在事件总线内部维护着一个事件与事件处理程序相映射的字典. (2) 利用反射,事件总线会将实现 ...
- Universal-Image-Loader完全解析--从源代码分析Universal-Image-Loader中的线程池
一般来讲一个网络访问就需要App创建一个线程来执行,但是这也导致了当网络访问比较多的情况下,线程的数目可能积聚增多,虽然Android系统理论上说可以创建无数个线程,但是某一时间段,线程数的急剧增加可 ...
- 机器学习算法实现解析——word2vec源代码解析
在阅读本文之前,建议首先阅读"简单易学的机器学习算法--word2vec的算法原理"(眼下还没公布).掌握例如以下的几个概念: 什么是统计语言模型 神经概率语言模型的网络结构 CB ...
- EventBus原理解析
前言 EventBus的核心思想是观察者模式 (生产/消费者编程模型) . SpringBoot+EventBus使用教程(一) SpringBoot+EventBus使用教程(二) 通过前面的文章我 ...
- EventBus完全解析--组件/线程间通信利器
github地址:https://github.com/greenrobot/EventBus 1, Android EventBus实战, 没听过你就out了 2, Android EventBu ...
- jQuery工作原理解析以及源代码示例
jQuery的开篇声明里有一段非常重要的话:jQuery是为了改变javascript的编码方式而设计的.从这段话可以看出jQuery本身并不是UI组件库或其他的一般AJAX类库.jQuery改变ja ...
随机推荐
- ajax错误信息
XMLHttpRequest.status状态码 1xx-信息提示 这些状态代码表示临时的响应.客户端在收到常规响应之前,应准备接收一个或多个1xx响应. 100-继续. 101-切换协议. 2xx- ...
- 模块-- HASH
模块 HASH 一 MD5 import hashlib h = hashlib.md5() # In [237]: h # Out[237]: <md5 HASH object @ 0x0 ...
- NOIp模拟赛三十四(yxq供题)
毒瘤yxq! 毒瘤yxq! 毒瘤yxq! 据yxq自己说,林导让他出题的时候要求是“代码量少”,“思维难度高”,“不涉及太复杂的算法”,而且“最好要让myh有一题做不出来”(狙击myh).于是今天的题 ...
- ES6学习笔记(二十二)ArrayBuffer
ArrayBuffer ArrayBuffer对象.TypedArray视图和DataView视图是 JavaScript 操作二进制数据的一个接口.它们都是以数组的语法处理二进制数据,所以统称为二进 ...
- thttpd 在S3C6410的移植-web服务程序的应用
1. 在VMWare 虚拟机上将arm-linux-gcc 4.3.1配置好:2. 下载thttpd软件包并解压:3. 在thttpd根目录下运行: ./configure:4. ...
- System and method for assigning a message
A processor of a plurality of processors includes a processor core and a message manager. The messag ...
- nginx和apache作为webserver的差别
1.两者所用的驱动模式不同. nginx使用的是epoll的非堵塞模式事件驱动. apache使用的是select的堵塞模式事件驱动. 2.fastcgi和cgi的差别 当用户请求web服务的时候.w ...
- 一:Java之面向对象基本概念
1.面向对象 面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm).其基本思想是使用对象.类.继承.封装.多态等基本概念来进行程序设计.从 ...
- 求一个数组的最大k个数(java)
问题描写叙述:求一个数组的最大k个数.如,{1,5,8,9,11,2,3}的最大三个数应该是,8,9,11 问题分析: 1.解法一:最直观的做法是将数组从大到小排序,然后选出当中最大的K个数.可是这种 ...
- 【Android UI】案例02 圆角边框、圆角背景的实现(shape)
本文主要分享圆角边框与圆角背景的实现方式.该方式的实现,须要了解shape的使用.该部分的具体介绍,请阅读博客http://blog.csdn.net/mahoking/article/details ...