EventBus 是一个基于观察者模式的事件发布/订阅框架,开发者能够经过极少的代码去实现组件,模块之间的通讯,而不须要以层层传递接口的形式去单独构建通讯桥梁。从而下降因多重回调致使的模块间强耦合,同时避免产生大量内部类。它拥有使用方便,性能高,接入成本低和支持多线程的优势,实乃模块解耦、代码重构必备良药。java
Android 中 提供了 Handler 来进行组件间的通讯,而 Handler 在使用上有不少不便,EventBus 的出现完美的解决了这些问题。git
用了那么久 EventBus 因此我决定本身实现一个,正好最近项目开始使用 Kotlin 来写,因此本文中的代码和例子所有使用 Kotlin 来完成。github
Github 地址:github.com/Werb/EventB…安全
前面说了 EventBus 是基于观察者模式,核心是事件。经过事件的发布和订阅实现组件之间的通讯,EventBus 默认是一个单例存在,在 Java 中还须要使用 Synchronized 来保证线程安全。通俗来说,EventBus 经过注册将全部订阅事件的方法储存在集合中,当有事件发布的时候,根据某些规则,匹配出符合条件的方法,调用执行,从而实现组件间的通讯。多线程
发布的事件至关于被观察者,注册的对象至关于观察者,被观察者和观察者是一对多的关系。当被观察者状态发生变化,即发布事件的时候,观察者对象将会获得通知并做出响应,即执行对应的方法。框架
EventBus 最终的目的,是在当有事件发生的时候,调用执行对应的方法,这里咱们采用注解的方式来标记执行的方法,最终经过反射来调用。async
Subscriber
是一个注解类,咱们经过 @Subscriber
关键字来标记方法。Subscriber
在声明的时候,有两个可选参数,tag: String
和 mode: ThreadMode
,而且这两个参数都有本身的默认值。ide
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION,AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
annotation class Subscriber(val tag: String = DEFAULT_TAG, val mode: ThreadMode = ThreadMode.POST)复制代码
tag
的目的是为了当有相同事件发生的时候,细化区分不一样的事件。函数
例如咱们定义了一个 SessionEvent
用于帐户登陆和登出时的信息传递,这时候咱们就能够定义 login
和 logout
两个 tag
来进行区分。固然你也彻底能够定义两个 LoginEvent
和 LogoutEvent
,这个字段的目的是为了能够更加方便灵活的操做代码。oop
tag
的默认值为 DEFAULT_TAG
。
mode
的目的是为了声明注解所标记的方法的执行环境。
mode
所表明的是 ThreadMode
类。这是一个枚举类,可选值为MAIN
,POST
, BACKGROUND
,即响应 Event 事件时,方法执行所在的线程。
mode
的默认值为 POST
。
在这里,我定义了一个空的接口 IEvent
,咱们经过 EventBus
发出的事件类须要实现这个接口,同时在经过注解定义事件执行方法的时候,须要将咱们接收的某个事件类做为方法的参数,有且只有一个参数,它就是咱们的被观察者。
一个完整的例子:
/** EventBus 发送的事件类 这个类的目的用于用户登陆登出的相关操做 */
class SessionEvent: IEvent复制代码
/** 登陆成功时发送 Event */
EventBus.post(SessionEvent(), "login")
/** 登出成功时发送 Event */
EventBus.post(SessionEvent(), "logout")复制代码
/** 登陆成功 默认在 POST 线程执行 */
@Subscriber(tag = "login")
private fun login(event: SessionEvent) {
// do something
}
/** 登陆成功 在 MAIN 线程执行,一般是一些 UI 上的操做 */
@Subscriber(tag = "logout", mode = ThreadMode.MAIN)
private fun logout(event: SessionEvent) {
// do something
}复制代码
前面说了,咱们须要将全部订阅事件的方法存储到一个集合中,当有事件发出的时候,咱们经过某些规则,匹配出符合条件的方法,调用执行。因此,首先咱们须要去定采用哪一种集合来存储,存储时的规则是什么。
这里咱们采用 MutableMap
来存储事件执行的方法。MutableMap
在 Kotlin 中表示为可变的,结构以下。
MutableMap<EventType, CopyOnWriteArrayList<Subscription>>
EventType
是惟一key(下面会介绍),经过key找到对应的执行方法。CopyOnWriteArrayList<Subscription>>
是一个线程安全的 List ,前面咱们说过了被观察者和观察者是一对多的关系因此这里使用 List,Subscription
事件执行方法的包装类(下面会介绍)。
EventType
类包含两个字段,eventClass: Class<IEvent>
和 tag: String
。
eventClass: Class<IEvent>
是咱们经过 EventBus
发出的事件类
tag: String
是咱们发出事件类时所指定的 tag
internal class EventType(private var eventClass: Class<IEvent>, private var tag: String) {
override fun equals(other: Any?): Boolean {
// 比较内存引用地址,相同则返回 true
if (this === other) {
return true
}
// 判断是否为空,是否属于同一中类型
if (other == null || (other.javaClass.name !== this.javaClass.name)) {
return false
}
// 能执行到这里,说明 obj 和 this 同类且非 null
val eventType = other as EventType
val tagJudge = tag == eventType.tag
val eventJudge = eventClass.name == eventType.eventClass.name
return tagJudge && eventJudge
}
override fun hashCode(): Int {
var hash = 7
hash = hash * 31 + eventClass.hashCode()
hash = hash * 31 + tag.hashCode()
return hash
}
}复制代码
经过这两个字段咱们就能够肯定一个惟一的key,准确的找到所对应的事件执行方法。
Subscription
是事件执行方法的包装类,它包含 subscriber: WeakReference<Any>
,targetMethod: Method
,threadMode: ThreadMode
这三个字段。
subscriber: WeakReference<Any>
这个就是咱们的观察者,它能够是一个 Activity
或 Fragment
或 Service
,咱们注册了这个观察者到咱们的 EventBus
中,当被观察者产生变化的时候,观察者调用执行对应的方法,而且这里使用弱引用来包装这个对象。targetMethod: Method
事件调用执行的具体方法。threadMode: ThreadMode
事件方法执行时所在的环境。internal class Subscription(val subscriber: WeakReference<Any>,
val targetMethod: Method,
val threadMode: ThreadMode) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || (other::class !== this::class)) {
return false
}
val subscription = other as Subscription
val judgeSubscriber = this.subscriber.get() === subscription.subscriber.get()
val judgeMethod = this.targetMethod.name == subscription.targetMethod.name
return judgeSubscriber && judgeMethod
}
override fun hashCode(): Int {
var hash = 7
hash = hash * 31 + subscriber.hashCode()
hash = hash * 31 + targetMethod.hashCode()
hash = hash * 31 + threadMode.hashCode()
return hash
}
}复制代码
好了,如今咱们肯定了 EventBus
的内部存储结构,接下来咱们就要说一下最关键的部分,EventBus
是如何注册 register
和发送 post
事件的,先看一下 EventBus
的代码。
/** object 表示单例 */
object EventBus {
private val executorService: ExecutorService = Executors.newCachedThreadPool()
private val subscriberMap = ConcurrentHashMap<EventType, CopyOnWriteArrayList<Subscription>>()
private val methodHunter = SubscriberMethodHunter(subscriberMap)
/** 注册订阅对象 */
fun register(subscriber: Any) {
executorService.execute {
methodHunter.findSubscribeMethods(subscriber)
}
}
/** 注销订阅对象 */
fun unRegister(subscriber: Any) {
executorService.execute {
methodHunter.removeSubscribeMethods(subscriber)
}
}
fun post(event: IEvent) {
post(event, DEFAULT_TAG)
}
fun post(event: IEvent, tag: String) {
val eventType = EventType(event.javaClass, tag)
val list = methodHunter.getMatchEventType(eventType)
list?.let {
EventDispatcher.dispatcherEvent(event, it)
}
}
}复制代码
executorService: ExecutorService
是一个线程池,咱们在注册和注销的时候,采用线程池,由于注册和注销是很频繁数量大的一个操做,同时单个任务处理的时间比较短,使用线程池能够很好的提升效率。subscriberMap = ConcurrentHashMap<EventType, CopyOnWriteArrayList<Subscription>>()
这个就是咱们存储被观察者与观察者的集合。methodHunter = SubscriberMethodHunter(subscriberMap)
这个就是咱们注册的核心类,下面会详细介绍。/** 注册订阅对象 */
fun register(subscriber: Any) {
executorService.execute {
methodHunter.findSubscribeMethods(subscriber)
}
}复制代码
subscriber: Any
是咱们的观察者对象,它能够是一个 Activity
或 Fragment
或 Service
。
举个例子来讲注册的流程,当咱们注册一个 Activity
到 EventBus
中时,咱们经过 methodHunter.findSubscribeMethods(subscriber)
方法,查找出当前 Activity
中被@Subscriber
关键字来标记方法,判断的规则以下:
@Subscriber
关键字标记IEvent
接口当肯定是咱们要找的方法以后,根据方法的参数 IEvent
和 tag
,生成一个 EventType
实例做为当前方法的惟一key,根据观察者对象 subscriber: Any
和找到的事件执行方法 method
以及事件执行环境 mode
,生成一个 Subscription
。
固然咱们如今还不能直接把它存储到咱们的集合中,回顾一下咱们的集合结构,MutableMap<EventType, CopyOnWriteArrayList<Subscription>>
,由于要先判断集合中是否已存在同样的 key,当存在则把当前 Subscription
添加到 CopyOnWriteArrayList<Subscription>>
中,这也是咱们重写 EventType
的 equals()
和 hashCode()
的缘由,具体代码以下。
/** 查找对象中有注解标记的执行方法 */
@Synchronized fun findSubscribeMethods(subscriber: Any) {
var clazz: Class<*>? = subscriber.javaClass
while (clazz != null && !isSystemClass(clazz.name)) {
val allMethods = clazz.declaredMethods
for (method in allMethods) {
val annotation = method.getAnnotation(Subscriber::class.java)
if (annotation != null) {
// 获取方法参数
val paramsTypeClass = method.parameterTypes
// 订阅函数只支持一个参数
if (paramsTypeClass != null && paramsTypeClass.size == 1) {
val paramsEvent = paramsTypeClass[0]
if (isImplementIEvent(paramsEvent)) {
method.isAccessible = true
@Suppress("UNCHECKED_CAST")
val eventType = EventType(paramsEvent as Class<IEvent>, annotation.tag)
val subscription = Subscription(WeakReference(subscriber), method, annotation.mode)
subscribe(eventType, subscription)
}
}
}
}
clazz = clazz.superclass
}
}
/** 根据 EventType 来肯定一对多的订阅关系 */
@Synchronized private fun subscribe(type: EventType, subscription: Subscription) {
var subscriptionLists: CopyOnWriteArrayList<Subscription>? = getMatchEventType(type)
if (subscriptionLists == null) {
subscriptionLists = CopyOnWriteArrayList()
}
if (subscriptionLists.contains(subscription)) {
return
}
subscriptionLists.add(subscription)
// 将事件类型key和订阅者信息存储到map中
subscriberMap.put(type, subscriptionLists)
}
/** 判断是否有已存在的 EventType */
internal fun getMatchEventType(type: EventType): CopyOnWriteArrayList<Subscription>? {
val keys = subscriberMap.keys
return keys.firstOrNull { it == type }?.let { subscriberMap[it] }
}复制代码
到这一步,咱们已经把观察者对象注册到了 EventBus
中,剩下的就是在须要的时候发送事件就能够了,因此接下来,咱们来看一下如何发送事件和执行事件订阅的方法。
fun post(event: IEvent) {
post(event, DEFAULT_TAG)
}
fun post(event: IEvent, tag: String) {
val eventType = EventType(event.javaClass, tag)
val list = methodHunter.getMatchEventType(eventType)
list?.let {
EventDispatcher.dispatcherEvent(event, it)
}
}复制代码
post
有两个方法,一个是使用默认的tag,一个是本身指定tag,这个方法很简单,咱们根据发送事件时指定的 IEvent
和 tag
,肯定出 EventType
,也就是咱们集合中惟一的 key,经过 key 获得 value,value 就是 CopyOnWriteArrayList<Subscription>
,也就是当前事件执行方法的集合,剩下的就是执行调用这些方法。
前面说了,咱们在实现事件执行方法的时候,会指定事件执行时的环境,MAIN
,POST
, BACKGROUND
,默认环境是 POST
线程,这样能够减小线程切换时带来的开销。
internal object EventDispatcher {
/** POST 线程即事件发出的线程 */
private val postHandler = PostEventHandler()
/** MAIN 线程 UI 线程 */
private val mainHandler = MainEventHandler()
/** BACKGROUND 线程,ExecutorService 线程池 */
private val asyncHandler = AsyncEventHandler()
fun dispatcherEvent(event: IEvent, list: CopyOnWriteArrayList<Subscription>) {
list.forEach {
val eventHandler = getEventHandler(it.threadMode)
eventHandler.handleEvent(it, event)
}
}
private fun getEventHandler(mode: ThreadMode): EventHandler {
return when (mode) {
ThreadMode.POST -> postHandler
ThreadMode.BACKGROUND -> asyncHandler
ThreadMode.MAIN -> mainHandler
}
}
}复制代码
正如上面的代码,咱们依据 threadMode
,选择符合的 EventHandler
,调用其 handleEvent(subscription: Subscription, event: IEvent)
去执行方法。
internal interface EventHandler {
fun handleEvent(subscription: Subscription, event: IEvent)
}复制代码
EventHandler
是一个接口,定义的事件执行时调用的方法 handleEvent()
,咱们须要依次实现三种环境的具体实现。
internal class PostEventHandler: EventHandler {
override fun handleEvent(subscription: Subscription, event: IEvent) {
try {
subscription.targetMethod.invoke(subscription.subscriber.get(), event)
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
throw e.targetException
}
}
}复制代码
internal class MainEventHandler: EventHandler {
private val handler = Handler(Looper.getMainLooper())
override fun handleEvent(subscription: Subscription, event: IEvent) {
handler.post({
try {
subscription.targetMethod.invoke(subscription.subscriber.get(), event)
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
throw e.targetException
}
})
}
}复制代码
AsyncEventHandler 后台线程采用线程池实现
internal class AsyncEventHandler: EventHandler {
private val bgExecutor = Executors.newCachedThreadPool()
override fun handleEvent(subscription: Subscription, event: IEvent) {
bgExecutor.execute {
try {
subscription.targetMethod.invoke(subscription.subscriber.get(), event)
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
throw e.targetException
}
}
}
}复制代码
三种环境中,核心的执行方法都是同样的,采用反射来调用具体事件的执行方法,仅仅是方法所在的执行环境不一样而已。
到目前为止,咱们的 EventBus 就完成了,经过注解的形式实现事件的执行方法,经过注册观察者对象,生成 key 和 value 创建关系存储到集合中,在事件发出的时候,查找出对应的事件方法集合,而后在指定的执行环境中调用。
有哪些写的不对的地方请提出,我会立刻改正
我是 wanbo。