EventBus的出现完美的解决了多个线程和多个activity、fragment之间的通信问题,下面我们来讲解一下具体的实现原理:
本篇不涉及任何源码,单纯讲一下它的流程,然后讲一下优缺点。
Eventbus 中有三个集合,这基本就是核心所在。
第一个集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//第一个集合是用来存储的
//key是数据类型的clazz
//value 是一个Subscription集合,
//Subscription对象是一个包装类,包含注册的时候传进去的对象 和对象中一个带 @Subscribe 注解的方法
public class A {
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept1(Person p){
}
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept2(Person p){
}
}
public class B {
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept3(Person p){
}
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept4(Person p){
}
}
public class C {
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept1(Student s){
}
@Subscribe (threadMode = ThreadMode.MAIN)
public void accept2(Student s){
}
}
1,当 A 类调用 EventBus.getDefault().register(this);进行注册之后 ,
subscriptionsByEventType集合长度为 1,第一个key是 Person.Class ,value 就是一个长度为2的CopyOnWriteArrayList对象,CopyOnWriteArrayList中
第一个Subscription 是A对象的实例和 A对象的 accept1方法封装成的 Subscription 对象,
第二个Subscription 是A对象的实例和 A对象的 accept2方法封装成的 Subscription 对象
2,当B类对象调用 EventBus.getDefault().register(this);进行注册之后 ,
subscriptionsByEventType集合长度为 1,第一个key是Person.Class ,value 就是一个长度为4的CopyOnWriteArrayList对象,CopyOnWriteArrayList中
第一个Subscription 是A对象的实例和 A对象的 accept1方法封装成的 Subscription 对象,
第二个Subscription 是A对象的实例和 A对象的 accept2方法封装成的 Subscription 对象,
第三个Subscription 是B对象的实例和 B对象的 accept3方法封装成的 Subscription 对象,
第四个Subscription 是B对象的实例和 B对象的 accept4方法封装成的 Subscription 对象,
3,当C类对象调用 EventBus.getDefault().register(this);进行注册之后 ,
subscriptionsByEventType集合长度为 2,
第一个key是 Person.Class ,第一个key对应的Value 就是一个长度为4的CopyOnWriteArrayList对象,
第一个CopyOnWriteArrayList对象中的四个对象《步骤2中的四个Subscription 》
第二个key是 Student.Class, 第二个key对应的value 是一个长度为 2的 CopyOnWriteArrayList 对象,
第二个CopyOnWriteArrayList对象中的两个个对象
第一个Subscription 是C对象的实例和 C对象的 accept1方法封装成的 Subscription 对象,
第一个Subscription 是C对象的实例和 C对象的 accept2方法封装成的 Subscription 对象,
总结一下 ,
- 1,所有订阅的方法中,有多少参数类型,subscriptionsByEventType 的长度就是多少,每个参数类型 对应一个list集合。
- 2,该参数类型有多少个订阅的方法 ,对应的list集合长度就是多少,每一个Subscription 对象都是订阅的方法和方法所在的类的 实例。
这个集合的作用就是用来 分发事件用的,当发事件的时候,根据分发的事件类型的Class ,拿到对应订阅的方法的集合CopyOnWriteArrayList对象,然后逐一遍历进行分发,这也就是分发的原理。当然真正的流程没有这么简单,还涉及到了线程切换等等,这里不细说,主要介绍主流程。
伪代码
public void register (Object subscriber){
Class<?> tagClass = tag.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(tagClass);
//通过反射拿到所有方法,SubscriberMethod封装的方法的参数类型,线程类型,优先级,粘性
synchronized (this) {
//遍历所有方法
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//拿参数类型
Class<?> eventType = subscriberMethod.eventType;
//进行封装 Subscription 除了包含方法所有参数之外还有tag的引用
Subscription newSubscription = new Subscription(tag, subscriberMethod);
//根据参数类型去第一个集合中判断是否有当前集合,如果没有就创建新的集合,如果有就根据优先级把当前的订阅方法封装添加进去
CopyOnWriteArrayList<Subscription> list = subscriptionsByEventType.get(eventType);
if (list == null) {
list = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//这里根据优先级添加,我没有写。
list.add(Subscription);
}
}
}
}
再说一下getDefult():getDefult()使用的单例双重锁创建了EventBus实例
第二个集合
private final Map<Object, List<Class<?>>> typesBySubscriber;
//这个集合的作用主要解除注册用的 ,
//key是注册时候的tag
//vlaue 的值是一个参数类型Clazz集合。
当有类进行注册的时候,对第一个集合进行操作的同时,也会对第二个集合进行操作,(上边的流程)
1,当 A 类调用 EventBus.getDefault().register(this);进行注册之后 ,
typesBySubscriber集合长度为 1,第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,
第一个Class是Person.Class,
第二个Class是Person.Class,
2,当B类对象调用 EventBus.getDefault().register(this);进行注册之后 ,
typesBySubscriber集合长度为 2,
第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,
《步骤一中的list 》
第二个Key是B对象,Vlaue 就是一个长度为2的List<Class<?>对象,
第一个Class是Person.Class,
第二个Class是Person.Class,
3,当C类对象调用 EventBus.getDefault().register(this);进行注册之后 ,
typesBySubscriber集合长度为 3,
第一个key的A对象 ,value 就是一个长度为2的List<Class<?>对象,
《步骤一中的list 》
第二个Key是B对象,Vlaue 就是一个长度为2的List<Class<?>对象,
《步骤二中的list 》
第三个Key 是C对象,value 就是一个长度为2的List<Class<?>对象,
第一个Class是Student.Class,
第二个Class是Student.Class,
在总结一下 ,
1,有多少个对象订阅了,typesBySubscriber的长度就是多少,每个订阅对象对应一个list集合。
2,每个订阅的对象中有多少个订阅的方法,list的长度就是多少,源码就是遍历所有订阅的方法,list把每个方法的参数添加进去。
这个集合主要的作用是用来解除注册的,
解除注册的时候,首先拿到要解除注册对象的所有方法的参数class的集合,然后遍历这个class集合,遍历的时候 通过第一个集合拿到每一个class所有的订阅方法,这里拿到的也是一个集合CopyOnWriteArrayList ,在遍历这个集合,凡是Subscription中 持有的对象 和当前要解除的对象一样的情况下,就把当前Subscription对象 从CopyOnWriteArrayList当中移除。遍历完所有list 集合之后就成功的解除了当前对象所有的订阅,最后从typesBySubscriber 把 当前对象保留的集合移除
伪代码
public synchronized void unregister(Object tag) {
// 拿到当前对象所有的订阅方法
List<Class<?>> subscribedTypes = typesBySubscriber.get(tag);
if (subscribedTypes != null) {
//遍历所有方法
for (Class<?> eventType : subscribedTypes) {
//拿到每个参数类型的所有订阅
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);
//如果订阅的方法 中tag的和当前解除的一样就移除
if (subscription.subscriber == tag) {
subscriptions.remove(i);
}
}
}
}
typesBySubscriber.remove(subscriber);
}
}
第三个集合的作用 作为粘性分发的作用
private final Map<Class<?>, Object> stickyEvents;
//第三个集合是粘性分发
//key是数据类型的clazz
//value 是数据对象,
事件分发 分为两种:正常分发 和粘性分发
第一种,正常分发
直接上伪代码
public void post(Object event) {
Class<?> eventClass = event.getClass();
//从第一个集合中,通过参数类型按到需要分发所有方法的集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventClass);
//遍历方法进行分发
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
//判断线程,切换线程的代码省略,简单说一下,四种类型
// 第一种 POSTING 不做线程切换
// 第二种 MAIN 如果发送的不在主线程 通过handler 切换线程
// 第三种 BACKGROUND 如果不是主线程的就不进行线程切换,如果是主线程就进行线程切换,通过线程池进行切换
// 第四种 ASYNC ,无论在不在主线程,都进行线程切换,通过线程池切换
//反射调用方法,把事件发送出
subscription.subscriberMethod.method.invoke(subscription.tag, event);
}
}
}
第二种粘性分发:先分发,后注册也能收到消息,原理就在此,正常分发,然后把数据存起来
1,粘性分发首先执行一遍上边正常的流程。
2,利用第三个集合 把分发的数据保存起来
再讲前边注册的流程中 最后一步,
伪代码
public void register(Object tag){
............
//第一个集合操作过程
//第二个集合操作过程
..........
//判断是粘性事件
if (subscriberMethod.sticky) {
//拿到需要分发的数据对象 ,然后单独对当前订阅的方法进行分发
Object stickyEvent = stickyEvents.get(eventType);
subscriberMethod.invoke(tag, stickyEvent);
}
}
最后简单串联一下逻辑:
EventBus原理,主要是通过注解和反射实现,将方法保存在公共队列中供其调用,首先在register()的时候,通过反射把当前类的所有方法遍历,然后把带有@Subscribe注解的方法保存在队列中,在调用的时候发送post方法,与队列中的方法进行匹配,这里只匹配方法的参数,如果一样的话就掉起该方法。
注册的时候
把当前对象所有注册方法放到第一个集合当中管理,为了分发 用。
同事第二个集合保存的数据就是记录了那些对象注册了,用来解除注册。
如果是粘性事件 去第三个集合中寻找对应的数据类型和数据,然后直接进行分发。
发送事件的时候
根据发送的事件类型找到所有需要发送对象的对应方法
解除注册的时候,
通过第二个集合找到所有注册的方法,然后进行逐个判断进行解除注册。
最后说一下个人所认为的优缺点
优点 :
可以跨模块发送消息。原理就是动态注册动态发送
缺点 : 接受事件类型
事件类型会定义很多
不同模块的消息类型必须两个模块都能引用到才行
不是流程式的代码,阅读性差。个人倾向于流程式的代码。