继Glide篇,RxJava2篇,OkHttp篇,Retrofit篇后的又一篇源码分析,要点如下:
- EventBus中用到的APT技术(annotationProcessor)
- EventBus的注册与反注册过程(register、unregister)
- EventBus事件的发送与接收(HandlerPoster、BackgroundPoster、AsyncPoster)
EventBus中用到的APT技术
EventBus作为事件总线,是观察者模式的典型实现。事件是被观察者,而观察者就是我们用@Subscribe注解的方法了。EventBus3.0允许以两种方式生成订阅表,既可以依靠APT技术在编译期通过事先生成的代码来构建订阅表,也可以在运行期通过反射来生成订阅表,显然前者在性能上要更好一些。
如果要采用编译期构建订阅表的方式,首先在build.gradle中声明编译期生成类的完整包名
|
|
然后在初始化时把编译期生成的MyEventBusIndex
实例插入到DefaultBus中
|
|
负责APT的模块是EventBusAnootationProcessor
,首先进入它的源码
|
|
上面截取了EventBusAnootationProcessor
中最核心的生成代码的部分:
- 首先根据build.gradle中事先声明的完整包名和类名创建一个索引类
- 在类中创建一个HashMap做订阅表,并对外提供
getSubscriberInfo(Class<?> subscriberClass)
方法,以便EventBus运行时从表中查阅需要的SubscribeInfo
writeIndexLines
是填充订阅表的主要方法,它将一个Class类作为key,将包含了该类里所有标记了Subscribe注解的Method和其他信息的SubscriberInfo作为value,put到订阅表中。writeCreateSubscriberMethods
方法的作用是解析每个Subscribe
注解的方法以及注解中包含的信息,它在解析后获得SubscribeMethodInfo
对象,该对象用来创建作为订阅表value的SimpleSubscribeInfo
EventBus的注册与反注册过程
上面已经分析过编译期生成订阅表的过程了,有了订阅表之后注册与反注册就十分简单了。先看注册部分的源码
|
|
上面的注释已经标注的很详细了,从上面的代码中可以看到,在我们平时调用EventBus.getDefault().register(this)
后执行了如下操作:
- 首先从
SubscriberMethodFinder
实例中根据注册实例的Class类型获取该注册类中的所有注解了Subscribe
的方法List<SubscriberMethod>
,至于SubscriberMethodFinder
中是如何解析获得这些订阅方法信息的问题我们后面再分析 - 接着第一步是更新
subscriptionsByEventType
这个map对象,该对象是EventBus中最重要的容器,保存了当前所有注册了的订阅方法。这一步将每一个subscriberMethod
和当前注册的实例subscriber
组成了一个新对象Subscription
,它是接收事件的基本单位。再依据subscriberMethod
中保存的当前方法的形参类型(即意图接收的事件类型),从subscriptionsByEventType
中取到对应的subscriptions
,将新的Subscription存入其中,完成注册事件的登记。 - 然后第二步是更新
typesBySubscriber
这个map对象,该对象把当前注册的实例作为key,把实例中所有订阅方法的参数Class类型组成的List作为value,保存在其中。这么做一方面可以方便的查询当前实例是否注册过,另一方当反注册时也能根据取到的types从subscriptionsByEventType
取到所有需要做remove的subscriptions
,进而分别从中移除不再需要监听事件的方法。 - 最后一步是处理sticky事件。当一个订阅方法的isSticky标注为true时,显然在注册的那一刻就应该检索目前所有的stickyEvents找出匹配类型的事件,并发射事件到该订阅方法。
接下来进入另一个重要的类SubscriberMethodFinder
看下它是如何根据注册实例的Class类型获取该类所有订阅方法的。
|
|
从以上代码可以分析出如下内容:
FindState
是SubscriberMethodFinder
的一个静态内部类,专门用来保存本次解析中获得的各种信息,为了节约内存开销由一个FindState数组充当对象池,默认大小为4- 当外部调用
findSubscriberMethods
方法时,首先会尝试从缓存METHOD_CACHE
中根据Class类型获取订阅方法信息。未取到则进入findUsingInfo
方法,尝试从编译期生成的索引表subscriberInfoIndexes
中取,如果仍未取到则走开销较大的反射方式findUsingReflection
方法获取。最终取到的subscriberMethods
再返回前会先存入缓存 - 在
findUsingReflection
方法中会反射获取所有方法并检查是否有Subscribe注解,是否添加过,是否只有一次参数,满足所有条件后,则将注解中的信息和当前Method封装成一个subscriberMethod保存起来
经过以上步骤,将所有注册类中的订阅方法都封装成了Subscription
并更新到了subscriptionsByEventType
中,注册过程结束,接下来看下反注册方法。
|
|
反注册过程很简单,大致可以视为3个步骤:
- 首先根据反注册实例的Class类型从
subscribedTypes
中取到所有types - 然后分别根据每个type从
subscriptionsByEventType
中取到所有subscriptions - 最后根据反注册的实例
subscriber
,从每个subscriptions中移除当初注册进去的方法
至此,subscriptionsByEventType
中不再包含被反注册了的类中的方法,反注册完成。
EventBus事件的发送与接收
先从post方法的源码开始分析
|
|
现在梳理一下发射事件的过程,这个过程包含很多函数调用,环环相扣:
- 先额外提一下
postSticky
方法,该方法相比post唯一多出的步骤就是将本次事件添加到stickyEvents,至于stickyEvent的用法在上面介绍SubscriberMethodFinder已介绍,不再赘述 post
方法是发射事件的起点,首先获取本次发射时的线程环境,然后事件入队,之后while循环中取出所有事件依次进入postSingleEvent方法postSingleEvent
方法主要判断本次事件是否涉及超类接收者,如果超类订阅方法也允许接收到事件,则分别发出超类事件和当前类事件postSingleEventForEventType
方法根据本次事件的Class类型,即type,从subscriptionsByEventType
中取到对应的事件订阅者subscription
,用于之后接收事件postToSubscription
方法是最终发出事件的地方,这里会根据事件发射时的线程环境和订阅者指定的接收环境来选择合适的poster发出事件,并触发subscription
来接收事件
下面来分别分析几种事件发送者:
HandlerPoster
:主线程发送者,本质是持有MainLooper的HandlerBackgroundPoster
: 单一子线程发送者,本质是Runnable,由线程池调度,同步块控制串行发送事件AsyncPoster
:异步子线程发送者,本质是Runnable,由线程池调度,由对应数量的线程并行发送事件
首先进入HandlerPoster的源码
|
|
由以上代码可见HandlerPoster中持有一个缓冲队列PendingPostQueue
,当有事件传入时首先将事件和订阅者组合封装成一个PendingPost
对象并入队。其实不仅仅是HandlerPoster,在BackgroundPoster和AsyncPoster中也有这么一个PendingPostQueue
。先进入它的源码:
|
|
可见它只是一个简单的单向链表实现的队列结构。当有对象入队时队尾向后移动移一位指向新对象,当有对象出队时则将当前表头返回,并将表头向后移一位。同时还用到了wait和notifyAll,支持队列阻塞等待
再看PendingPost,它只是一个简单的实体类,持有event和subscription,并且为了节省内存开销,在类第一次加载时会创建一个pendingPostPool做对象池,对外提供obtainPendingPost来复用PendingPost。
再回到HandlerPoster,当PendingPoster入队后紧接着会发一个空Message到Handler,从而触发handleMessage,保证在主线程执行最终的事件接收eventBus.invokeSubscriber(pendingPost)
,那么这个invokeSubscriber方法又是如何实现的呢?进入源码:
|
|
只有一行代码,直接从subscription中取出当初注册时存入的实例和方法,将event作为参数,以反射的形式触发该实例的该方法,由此订阅方法就接收到了事件,整个流程结束。
最后再看一下BackgroundPoster和AsyncPoster的源码:
|
|
|
|
两者在实现上很像,都实现了Runnable接口,由线程池来执行,确保在子线程中完成事件的接收与触发。所使用的线程池默认是newCachedThreadPool
,该线程池的特点是按需动态创建与回收线程,正好兼容上面串行执行和并行执行两种场景。在BackgroundPoster中由于加了同步锁,线程池只需要提供单线程串行发送事件即可,在AsyncPoster中线程池会提供与并发事件数相当的线程来并发发送事件,需要注意控制并发量。
最后
到此EventBus源码分析就结束了,EventBus除了实现了经典的观察者模式以外,还提供了Sticky,Inheritance,线程调度等便利的功能,帮助我们减少大量的回调,充分解耦各模块。但要注意不要忘记反注册,否则注册的实例会一直保存在EventBus中造成内存泄漏。EventBus相比其他框架需要记录、处理大量数据,且这些数据间还维持着各种映射关系,它对数据的合理封装也是很值得我们借鉴的一点(ゝ∀・)b
声明:本站所有文章均为原创或翻译,遵循署名-非商业性使用-禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名(Est)及原贴地址