最近因为换工作的一些琐事搞的我一个头两个大,也没怎么去学新东西,实在是有些愧疚。新项目用到了EventBus3.0,原来只是听说EventBus的鼎鼎大名,一直没仔细研究过。趁着周末有些时间,研究下代码,也算没有虚度光阴。
EventBus GitHub : https://github.com/greenrobot/EventBus
EventBus
是greenrobot出品的一个用于Android中事件发布/订阅的库。以前传递对象可能通过接口、广播、文件等等,尤其像同一个Activity
两个Fragment
之间采用接口传递对象,十分的麻烦,而且耦合度较高。使用EventBus
之后,这些将不再是问题。盗用GiHub上EventBus的一张图。
可以看到,发布者(Publisher
)使用post()
方法将Event
发送到Event Bus
,而后Event Bus
自动将Event
发送到多个订阅者(Subcriber
)。这里需要注意两个地方:(1)一个发布者可以对应多个订阅者。(2)3.0以前订阅者的订阅方法为onEvent()
、onEventMainThread()
、onEventBackgroundThread()
和onEventAsync()
。在Event Bus3.0
之后统一采用注解@Subscribe
的形式,具体实现方式见下文。
新建两个Activity
,花3s扫一下即可。代码如下:
在MainActivity
的onCreate()
/onDestroy()
中分别注册/反注册EventBus
。然后写了四个测试ThreadMode
的方法,调用时机注释的很清楚,就不赘述了。最后在SecondActivity
的主线程和子线程中分别调用Post()
方法,注意,这里Post()
方法的参数为Object
类型,这也就意味着我们传递任何对象都是可以的,例如JavaBean
、List<E>
等等都是可以的,这里为了方便演示直接传递了String
。Log信息如下:
第一张图中发布者发送线程为主线程,即Post thread = 1
,在订阅者收到消息时,ThreadMode = Main
和ThreadMode = Posting
的方法都在主线程调用,ASYNC
和BACKGROUND
都新开了线程。图二对比注释同理。
除此之外,Subscribe
注解还支持priority
和sticky
属性。priority
设置接收者的优先级,默认值为0。优先级高的方法先被调用,在方法调用完成后可以调用EventBus.getDefault().cancelEventDelivery(event) ;
终止优先级低的方法的调用。sticky
为粘性事件,默认为关闭状态。能够收到订阅之前发送到的最后一条消息,并且发送的方法不再是post()
而是postSticky()
。
EventBus
是Very的好用。耦合度大大的降低,而且代码十分优雅。它是怎么就做到了这么优雅的呢?知其然,知其所以然。下面就开始一步步的分析。
Subscribe
对注解不了解的同学可以看下这篇博客。
注解Subscribe
在运行时解析,且只能加在METHOD
上。其中有三个方法,threadMode()
返回类型ThreadMode
为枚举类型,默认值为POSTING
,sticky()
默认返回false
,priority()
默认返回0。
Register
流程EventBus#getDefault()
EventBus
采用双重校验锁设计为一个单例模式,奇怪的在于虽然设计为单例模式,但是构造方法确实public
类型,这不是坑爹嘛!难道greenrobot
在设计EventBus
获取实例方法的时候在打LOL,一不小心打错了?原来啊,EventBus
默认支持一条事件总线,通常是通过getDefault()
方法获取EventBus
实例,但也能通过直接new EventBus
这种最简单的方式获取多条事件总线,彼此之间完全分开。设计之思想不禁让人拍案叫绝。
EventBus#register()
首先得到订阅者的报名.类名,即哪个具体类注册。然后调用subscriberMethodFinder.findSubscriberMethods(subscriberClass)
,从方法名和返回值来看,findSubscriberMethods()
的作用应该是遍历查找订阅者中所有的订阅方法。
SubscriberMethodFinder#findSubscriberMethods()
注意subscriberMethods.isEmpty()
,如果注册了EventBus
,但却没有使用注解Subscribe
是会出现EventBusException
异常的。下面跟进findUsingInfo()
方法。
SubscriberMethodFinder#findUsingInfo()
findState.subscriberInfo
默认null
,那么就进入到findUsingReflectionInSingleClass(findState)
,先看下这个方法,等下还要返回来看。
SubscriberMethodFinder#findUsingReflectionInSingleClass()
接下来返回SubscriberMethodFinder#findUsingInfo()
接着看,在findUsingInfo()
中循环执行完后return getMethodsAndRelease(findState)
在getMethodsAndRelease()
中将findState
置空,存放进FIND_STATE_POOL
数组,最后返回findState.subscriberMethods
。返回EventBus#register()
。
EventBus#register()
调用SubscriberMethodFinder#findSubscriberMethods()
后,以List<SubscriberMethod>
形式返回了订阅者所有的订阅事件。然后遍历执行subscribe()
方法。看样子应该是遍历List<SubscriberMethod>
,然后将订阅者和订阅事件绑定。没撒好说的,跟进subscribe()
EventBus#subscribe()
EventBus#Register()
其实只做了三件事:
1. 查找订阅者所有的订阅事件
2. 将订阅事件作为key
,所有订阅了此订阅事件的订阅者作为value
存放进subscriptionsByEventType
3. 将订阅者作为key
,订阅者的所有订阅事件作为value
存放进typesBySubscriber
至此,EventBus.getDefault().register(this)
流程完毕。
Post
流程EventBus#getDefault()
获取EventBus实例
。和Register
流程中一样,不再赘述。
EventBus#post()
上面代码中currentPostingThreadState
为ThreadLocal<PostingThreadState>
对象,对ThreadLocal<>
机制不了解的同学,可以查看这篇博客。下面跟进postSingleEvent()
方法。
EventBus#postSingleEvent()
EventBus#lookupAllEventTypes()
现在假设传递的数据为Person
类,而Person
类实现了IPerson
接口。通过上面的分析可以得出结论:在传递对象(Person
)的时候,订阅事件中参数为被传递对象的所有父类订阅事件(IPerson
)也都会被调用。笔者已经验证通过,感兴趣的同学可以再验证一下。
EventBus#postSingleEventForEventType()
在EventBus#register()
最后总结道:将订阅事件作为key
,所有订阅了此订阅事件的订阅者作为value
存放进subscriptionsByEventType
。这里就依据订阅事件然后查找对应所有的订阅者。注意:由于遍历订阅事件参数所有父类的原因,一个订阅事件的Post
第一次执行postToSubscription()
时,subscription
参数,遍历时为订阅事件的订阅者。之后再调用postToSubscription()
时,subscription
参数都为订阅时间父类的订阅者。而event
参数则一直是订阅事件中的唯一参数(最底层子类)。
EventBus#postToSubscription()
看到这里差不多可以松口气,终于要分发调用订阅者的订阅事件了!写了整整一下午,容我抽支烟再。
首先根据ThreadMode
确定分发类型。这里以最常用的Main
为例,其余两个Poster
同理。如果是isMainThread=true
,那么直接调用invokeSubscriber()
,否则调用mainThreadPoster.enqueue()
。下面分别解释这两种情况。
EventBus#invokeSubscriber()
没撒好说的,直接反射调用订阅者的订阅事件。注意:参数event
是子类对象,就算调用订阅事件中唯一参数是参数的父类,那么传递的仍然是子类对象。笔者已经验证,感兴趣的同学可以自行验证。然后查看HandlerPoster#enqueue()
。
HandlerPoster#enqueue()
EventBus#invokeSubscriber()
至此,订阅者在相应线程调用订阅事件完成,EventBus.getDefault().Post()
流程完毕。
EventBus#Post()
也只做了三件事
1. 根据订阅事件在subscriptionsByEventType
中查找相应的订阅者
2. 分发订阅者的订阅事件调用线程
2. 通过反射调用订阅者的订阅事件
unregister
流程EventBus#getDefault()
获取EventBus实例
。和Register
流程中一样,不再赘述。
EventBus#unregister()
EventBus#unsubscribeByEventType()
在EventBus#register()
最后总结道:
将订阅事件作为key
,所有订阅了此订阅事件的订阅者作为value
存放进subscriptionsByEventType
。
将订阅者作为key
,订阅者的所有订阅事件作为value
存放进typesBySubscriber
。
现在要反注册咯。移除相应的key
、value
即可。EventBus3.0
的使用及源码解析到此结束,Have a nice day~