什麼是 EventBus
EventBus 是一個基於觀察者模式的事件釋出/訂閱框架,開發者可以通過極少的程式碼去實現元件,模組之間的通訊,而不需要以層層傳遞介面的形式去單獨構建通訊橋樑。從而降低因多重回撥導致的模組間強耦合,同時避免產生大量內部類。它擁有使用方便,效能高,接入成本低和支援多執行緒的優點,實乃模組解耦、程式碼重構必備良藥。
Android 中 提供了 Handler 來進行元件間的通訊,而 Handler 在使用上有很多不便,EventBus 的出現完美的解決了這些問題。
用了那麼久 EventBus 所以我決定自己實現一個,正好最近專案開始使用 Kotlin 來寫,所以本文中的程式碼和例子全部使用 Kotlin 來完成。
Github 地址:github.com/Werb/EventB…
EventBus 的原理
前面說了 EventBus 是基於觀察者模式,核心是事件。通過事件的釋出和訂閱實現元件之間的通訊,EventBus 預設是一個單例存在,在 Java 中還需要使用 Synchronized 來保證執行緒安全。通俗來講,EventBus 通過註冊將所有訂閱事件的方法儲存在集合中,當有事件釋出的時候,根據某些規則,匹配出符合條件的方法,呼叫執行,從而實現元件間的通訊。
釋出的事件相當於被觀察者,註冊的物件相當於觀察者,被觀察者和觀察者是一對多的關係。當被觀察者狀態發生變化,即釋出事件的時候,觀察者物件將會得到通知並作出響應,即執行對應的方法。
EventBus 具體實現
EventBus 最終的目的,是在當有事件發生的時候,呼叫執行對應的方法,這裡我們採用註解的方式來標記執行的方法,最終通過反射來呼叫。
Subscriber
Subscriber
是一個註解類,我們通過 @Subscriber
關鍵字來標記方法。Subscriber
在宣告的時候,有兩個可選引數,tag: String
和 mode: ThreadMode
,並且這兩個引數都有自己的預設值。
@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)複製程式碼
- @Retention(AnnotationRetention.RUNTIME) 宣告在執行時使用註解
- AnnotationTarget.FUNCTION 宣告註解時作用在方法上
- AnnotationTarget.PROPERTY_GETTER 宣告屬性存在 GET
- AnnotationTarget.PROPERTY_SETTER 宣告屬性存在 SET
tag
tag
的目的是為了當有相同事件發生的時候,細化區分不同的事件。
例如我們定義了一個 SessionEvent
用於賬戶登入和登出時的資訊傳遞,這時候我們就可以定義 login
和 logout
兩個 tag
來進行區分。當然你也完全可以定義兩個 LoginEvent
和 LogoutEvent
,這個欄位的目的是為了可以更加方便靈活的操作程式碼。
tag
的預設值為 DEFAULT_TAG
。
mode
mode
的目的是為了宣告註解所標記的方法的執行環境。
mode
所代表的是 ThreadMode
類。這是一個列舉類,可選值為MAIN
,POST
, BACKGROUND
,即響應 Event 事件時,方法執行所在的執行緒。
- MAIN 主執行緒 UI 執行緒
- POST 事件發出所在的執行緒
- BACKGROUND 子執行緒
mode
的預設值為 POST
。
IEvent
在這裡,我定義了一個空的介面 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
來儲存事件執行的方法。MutableMap
在 Kotlin 中表示為可變的,結構如下。
MutableMap<EventType, CopyOnWriteArrayList<Subscription>>
EventType
是唯一key(下面會介紹),通過key找到對應的執行方法。CopyOnWriteArrayList<Subscription>>
是一個執行緒安全的 List ,前面我們說過了被觀察者和觀察者是一對多的關係所以這裡使用 List,Subscription
事件執行方法的包裝類(下面會介紹)。
EventType
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
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)
這個就是我們註冊的核心類,下面會詳細介紹。
註冊 Register
/** 註冊訂閱物件 */
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] }
}複製程式碼
傳送事件 POST
到這一步,我們已經把觀察者物件註冊到了 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>
,也就是當前事件執行方法的集合,剩下的就是執行呼叫這些方法。
事件分發 EventDispatcher
前面說了,我們在實現事件執行方法的時候,會指定事件執行時的環境,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)
去執行方法。
事件執行 EventHandler
internal interface EventHandler {
fun handleEvent(subscription: Subscription, event: IEvent)
}複製程式碼
EventHandler
是一個介面,定義的事件執行時呼叫的方法 handleEvent()
,我們需要依次實現三種環境的具體實現。
- PostEventHandler
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
}
}
}複製程式碼
- MainEventHandler UI 執行緒
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。