EventBus源码分析之发布订阅模型
EventBus事件总线模式如下图:
本文主要从两个方面介绍源码:
1. 订阅者是如何注册到事件中心的; 2. 发布者发布了事件之后,事件中心是如何将事件调用到合适的订阅方法的。
订阅者注册到事件中心
订阅者注册到事件中心需要调用如下代码:
EventBus.gtetDefault().register(this)订阅者可以是任何对象,唯一的要求是内部有@Subscribe修饰的方法,该方法是有一定要求的,这可以在后面的源码中看到EventBus对该方法的要求。
EventBus.getDefault()
该方法的代码如下:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}可以看到EventBus是一个单例模式,也很好理解,毕竟管理者所有的订阅和和事件,有且只能有一个,单例决定了EventBus只能用于单个进程中的发布-订阅。
EventBus.register()
register()方法的参数是Object,说明任何对象都可以是订阅者,该方法源码如下:
从代码中可以看出主要有两步: 1. 找到@Subscribe修饰的方法,以SubscriberMethod对象表示方法的信息; 2. 将对象、方法作为元组注册到事件中心。 下面首先看一下SubscriberMethod的定义:
SubscriberMethod类的定义
上面就是其主要字段,一个合格的订阅方法(以Method对象表示)+@Subscribe注解中的信息:ThreadMode、EventType、priority、sticky。methodString字段是用来比较两个对象SubscriberMethod对象是否相等的依据。
SubScriberMethodFinder.findSubscriberMethods()
SubscriberMethodFind是用来找@Subscribe注解了方法的工具类,findSubscriberMethosd()的定义如下:
如果使用默认的EventBus,那么ignoreGeneratedIndex为false;由于查找订阅方法是一个耗时操作,因此SubscriberMethodFinder这儿对方法列表进行了缓存;这里先暂时不管该参数,那么僵使用findUsingReflection()方法进行查找,该方法的定义如下:
SubscriberMethod.findUsingReflection()
这里可以看到,对FindState对象做了一个容量为4的对象池。由此可以推断FindState是一个相对重量级的对象,所以才做了对象池,其定义如下:
FindState类定义
FindState是SubscriberMethodFinder的静态内部类:
这里面skipSuperClasses默认为false,说明默认是不忽略父类的,也就说明对象A注册到了事件中心,也将其向上的继承结构注册到了事件中心,不过想想也好理解,因为EventBus要求订阅方法必须是public、non-staic、non-abstract,那么父类的这些方法也就是子类的,因此需要向上查找。关于订阅方法的签名要求,下面会有源码说明。 查找的核心逻辑是这个循环:
SubscriberMethodFinder.findUsingReflectionInSingleClass()
首先,从抛出的异常,可以看到几点: 1. @Subscribe修饰的方法只能有一个参数 2. @Subscriber修饰的方法必须是public、non-static、non-abstract 当符合了条件并且是@Subscribe注解修饰的方法,如果checkAnd()返回true,那么将Method和注解信息封装成SubscriberMethod保存到FindState中的列表中。
FindState.checkAnd()
从注解中可以看到,执行两个层次的检查: 1. 第一层检查,只检查事件类型;如果该事件类型在该类中第一次出现,那么直接返回true; 2. 第二层检查,需要完整的检查方法签名,这种情况发生在该类中有多个方法同时订阅了某一事件类型。
这里我们需要分析,当一个事件类型出现了两个及其以上的订阅方法时,就会进入到二层检查;而从代码中可以看到,如果有多个订阅同一事件的方法,那么existing将会在method和findstate中来回切换。
FindState.checkAndWithMethodSignature()
这里可以看到subscriberClassByMethodKey这个Map中的Key的形式是MethodName>EventType,在同一个类中,MethodName肯定是不相同的,那么key就不会相同,那么methodClassOld将一直为null,那么该方法将一直返回true;如果父类中也有该方法并且也是同一事件的订阅方法,那么在查找父类的订阅方法时,methodClassOld将不为null。 至此,只要checkAnd返回true,那么将一直向FindState中添加订阅方法,而一旦父类中发现了相同的方法,那么不添加,因此子类中已经添加过了。
FindState.moveToSuperClass
在单个类中查找完订阅方法,将调用moveToSupperClass()将clazz字段移到父类,其定义如下:
当把订阅者整个继承结构的订阅方法找完之后,调用了getMethodsAndRelease()方法,该方法的定义如下:
SubscriberMethodFinder.getMethodsAndRelease()
该方法主要完成两步: 1. 从FindState中把checkAnd()返回结果为true时保存的SubScriberMethod取出; 2. 回收FindState。
至此,获取到了订阅者中的所有订阅方法,下一步是将这些信息保存到事件中心,以备后续查找进行分发。
例子
下面以一个例子,说明子类重载父类的订阅方法时,父类中的方法将不再作用。
在Activity A中初始化MagazineSubscriber,Activity B中发布一个Magezine事件,Log日志如下:
可以看到父类中的方法没有打出日志。
EventBus.subscribe()
在找到了订阅方法后,需要将其保存起来,subscribe()方法是在synchronized同步块中的。源码如下:
上面的代码主要做两步: 1. 根据事件类型获取订阅元组列表,按照优先级顺序保存到List中; 2. 处理事件是Sticky的情况;可以看到最终都调用checkPostStickyEventToSubscription()方法;关于Sticky事件在EventBus配置、粘性事件、优先级和取消事件分发博客中有介绍。 一般来说,如果是Sticky事件,那么stickyEvents将会有结果的,下面看一下checkPostStickyEvevtToSubscription。
EventBus.checkPostEventToSubscription()
可以看到将会调用postToSubscription()方法,这里有涉及线程分发的选项,关于线程分发的知识,可以参考EventBus的线程分发,下一篇准备介绍线程分发的源码。这里就以POSTING case往下继续看。
可以看到,这儿就是通过反射去调用订阅者的订阅方法。 至此,可以分析完了订阅者是如何将自己订阅到事件中心的,要点有如下几点: 1. EventBus保存了订阅者以及其父类中所有@Subscribe注解了的方法; 2. 订阅者+订阅方法是一个元组; 3. 如果事件是Sticky的,那么将使用反射进行调用;如果不是Sticky的,那么保存在EventBus中的List中。
由此带来的思考,由于EventBus保存了所有的订阅信息:订阅者+订阅方法,会不会占用内存很大?
发布者发布事件,事件如何到订阅方法的
其实看完上面的代码,应该有个大体思路了,东西都保存在了EventBus中,发布者发完事件,EventBus根据事件去找到所有订阅方法,然后反射调用就OK了,下面我们将实践看一下,是不是这么一个步骤。
EventBus.post()
一切从发布者的post()方法说起,源码如下:
上面涉及ThreadLocal,不了解的朋友可以参考ThreadLocal源码分析。PostThreadState会为每个线程创建一份,然后共用。同一个线程中发布的事件都会存到它的List中。 postSingleEvent()就是发送单个事件的方法,
从上面可以看到,核心代码是调用了postSingleEventForEventType()方法,该方法的代码如下:
可以看到最终调用了postToSubscription()方法将事件发布给订阅者,该方法在上面已经碰到了,最终是通过反射进行调用的;这样依次将每一个事件完成了发布。
取消事件分发
上面涉及到一个参数,PostingThreadState的canceled参数,该参数会在取消事件时标志。取消事件分发是在事件消费方法中调用cancelEventDelivery()方法,该方法的限制场景是ThreadMode是POSTING的情景下,下面会说明原因。该方法的代码如下:
上面说过,每个线程有一个PostingThreadState,而分发方法和订阅方法都会获取PostingThreadState进行标志位的设置来达到消息通信的,这要求必须得是同一个对象,也就是说发布和订阅是在同一个线程中,而ThreadMode为POSTING的情况下,发布和事件消费是在同一个线程中,这儿也能想象该类为啥叫PostingThreadState了。
总结
经过上面的源码分析,可以理解事件中心是如何保存订阅者的,订阅者为啥只需调用register()方法,其他就可以什么都不管了,因此事件中心会利用反射找出@Subscribe注解了的方法,然后保存起来;发布者为啥只要post()出事件,剩下的就不要管了,因为事件中心会去寻找出之前保存的订阅者以及订阅方法,然后通过反射进行调用。
Last updated