如何自己實現一個 EventBus

什么是 EventBus

EventBus 是一個基于觀察者模式的事件發(fā)布/訂閱框架,開發(fā)者可以通過極少的代碼去實現組件,模塊之間的通信,?而不需要以層層傳遞接口的形式去單獨構建通信橋梁。從而降低因多重回調導致的模塊間強耦合,同時避免產生大量內部類。它擁有使用方便,性能高,接入成本低和支持多線程的優(yōu)點,實乃模塊解耦、代碼重構必備良藥。

Android 中 提供了 Handler 來進行組件間的通信,而 Handler 在使用上有很多不便,EventBus ?的出現完美的?解決了這些問題。

用了那么久 EventBus 所以我決定自己實現一個,正好最近項目開始使用 Kotlin 來寫,所以本文中的代碼和例子全部使用 Kotlin 來完成。

Github 地址:https://github.com/Werb/EventBusKotlin

EventBus 的原理

前面說了 EventBus 是基于觀察者模式,核心是事件。通過事件的發(fā)布和訂閱實現組件之間的通信,?EventBus 默認是一個單例存在,在 Java 中還需要使用 Synchronized 來保證線程安全。通俗來講,EventBus 通過注冊將所有訂閱事件的方法儲存在集合中,當有事件發(fā)布的時候,根據某些規(guī)則,匹配出符合條件的方法,調用執(zhí)行,從而實現組件間的通信。

發(fā)布的事件相當于被觀察者,注冊的對象相當于觀察者,被觀察者和觀察者是一對多的關系。當被觀察者狀態(tài)發(fā)生變化,即發(fā)布事件的時候,觀察者對象將會得到通知?并作出響應,即執(zhí)行對應的方法。

EventBus 具體實現

EventBus 最終的?目的,是在當有事件發(fā)生的時候,調用執(zhí)行對應的方法,這里我們采用注解的方式來標記執(zhí)行的方法,最終通過反射來調用。

Subscriber

Subscriber 是一個注解類,我們通過 @Subscriber關鍵字來標記方法。Subscriber 在聲明的時候,有兩個可選?參數,tag: Stringmode: 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 的目的是為了?當有相同事件發(fā)生的時候,細化區(qū)分不同的事件。

例如我們定義了一個 SessionEvent 用于賬戶登錄和登出時的信息傳遞,這時候我們就可以定義 loginlogout 兩個 tag 來進行區(qū)分。當然你也完全可以定義兩個 LoginEventLogoutEvent,這個字段的目的是為了可以更加方便靈活的操作代碼。

tag 的默認值為 DEFAULT_TAG

mode

mode 的目的是為了?聲明注解所標記的方法的執(zhí)行環(huán)境。

mode 所代表的是 ThreadMode 類。這是一個枚舉類,可選值為MAINPOSTBACKGROUND,?即響應 Event 事件時?,方法執(zhí)行所在的線程。

  • MAIN 主線程 UI 線程
  • POST 事件發(fā)出所在的線程
  • BACKGROUND 子線程

mode 的默認值為 POST ?。

IEvent

在這里,我?定義了?一個空的?接口 IEvent ,我們通過 EventBus 發(fā)出的事件類需要實現這個接口,同時在通過?注解定義事件執(zhí)行方法的時候,需要將我們接收的某個事件類作為方法的參數,有且只有一個參數,它就是我們的被觀察者?。

一個完整的例子:

/** EventBus 發(fā)送的事件類 這個類的目的用于用戶登錄登出的相關操作 */
class SessionEvent: IEvent
/** 登錄成功時發(fā)送 Event */
EventBus.post(SessionEvent(), "login")
/** 登出成功時發(fā)送 Event */
EventBus.post(SessionEvent(), "logout")
/** 登錄成功 默認在 POST 線程執(zhí)行 */
@Subscriber(tag = "login")
private fun login(event: SessionEvent) {
    // do something
}

/** 登錄成功 在 MAIN 線程執(zhí)行,通常是一些 UI 上的操作 */
@Subscriber(tag = "logout", mode = ThreadMode.MAIN)
private fun logout(event: SessionEvent) {
    // do something
}

??前面說了?,我們需要將所有訂閱事件的方法存儲到一個集合中,當有事件?發(fā)出的時候,我們通過某些規(guī)則,匹配出符合條件的方法,調用執(zhí)行。所以,?首先我們需要去定采用哪種集合來存儲,存儲時的規(guī)則是什么。

MutableMap

這里我們采用 MutableMap 來存儲事件執(zhí)行的方法。MutableMap 在 Kotlin 中表示為可變的,結構如下。

MutableMap<EventType, CopyOnWriteArrayList<Subscription>>

?EventType 是唯一key(下面會介紹),通過key找到對應的執(zhí)行方法。
CopyOnWriteArrayList<Subscription>> 是?一個線程安全的 List ,前面我們說過了?被觀察者和觀察者是一對多的關系所以這里使用 List,Subscription事件執(zhí)行方法的?包裝類(下面會介紹)。

?EventType

?EventType 類包含兩個字段,eventClass: Class<IEvent>tag: String

  • eventClass: Class<IEvent> 是我們通過 EventBus 發(fā)出的事件類

  • tag: String 是我們?發(fā)出事件類時所指定的 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
        }

        // 能執(zhí)行到這里,說明 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,準確的找到所對應的事件執(zhí)行方法。

Subscription

Subscription 是事件執(zhí)行方法的?包裝類,它包含 subscriber: WeakReference<Any>targetMethod: MethodthreadMode: ThreadMode 這三個字段。

  • subscriber: WeakReference<Any> 這個就是我們的觀察者,它可以是一個 ActivityFragment?Service,?我們注冊了這個觀察者到我們的 EventBus 中,當?被觀察者產生變化的時候,觀察者調用執(zhí)行對應的方法,并且這里使用弱?引用來包裝這個對象。
  • targetMethod: Method 事件調用執(zhí)行的具體方法。
  • threadMode: ThreadMode 事件?方法執(zhí)行時所在的環(huán)境。
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 和發(fā)送 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 是我們的觀察者對象,它可以是一個 ActivityFragment?Service

舉個例子來說注冊的流程,當我們注冊一個 ActivityEventBus 中時,我們通過 methodHunter.findSubscribeMethods(subscriber) 方法,查找出當前 Activity 中被@Subscriber關鍵字來標記方法,?判斷的規(guī)則如下:

  1. @Subscriber 關鍵字標記
  2. ?訂閱函數只支持一個參數
  3. 訂閱函數的參數是否實現了 IEvent 接口

當確定是我們要找的方法之后,根據方法的參數 IEventtag,?生成一個 EventType 實例作為當前方法的唯一key,根據觀察者對象 subscriber: Any 和找到的事件執(zhí)行方法 method 以及事件執(zhí)行環(huán)境 mode,生成一個 Subscription

當然我們現在還不能直接把它存儲到我們的集合中,回顧一下我們的集合結構,MutableMap<EventType, CopyOnWriteArrayList<Subscription>>,因為要先判斷集合中是否已存在?一樣的 key,當存在?則把當前 Subscription 添加到 CopyOnWriteArrayList<Subscription>> 中,這也是我們重寫 EventTypeequals()hashCode() 的原因,?具體代碼如下。

/** 查找對象中有注解標記的執(zhí)行方法 */
@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] }
}

發(fā)送事件 POST

?到這一步,我們已經把觀察者對象注冊到了 EventBus 中,剩下的就是在?需要的時候發(fā)送事件?就可以了,所以接下來,我們來看一下如何發(fā)送事件和執(zhí)行事件?訂閱的方法。

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,這個方法很簡單,我們根據發(fā)送事件時指定的 IEventtag,確定出 EventType,也就是我們集合中唯一的 key,通過 ke?y 得到 value,value 就是 CopyOnWriteArrayList<Subscription>,也就是當前事件執(zhí)行方法的集合,剩下的就是執(zhí)行調用這些方法。

事件分發(fā) EventDispatcher

前面說了,我們在實現事件執(zhí)行方法的時候,會?指定事件執(zhí)行時的環(huán)境,MAINPOSTBACKGROUND,默認環(huán)境是 POST 線程,這樣可以減少線程切換時帶來的開銷。

internal object EventDispatcher {

    /** POST 線程即事件發(fā)出的線程 */
    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) 去執(zhí)行方法。

事件執(zhí)行 EventHandler

internal interface EventHandler {
    fun handleEvent(subscription: Subscription, event: IEvent)
}

EventHandler 是一個接口,定義的事件執(zhí)行時調用的方法 handleEvent(),?我們需要依次實現三種環(huán)境的具體實現。

  • 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
            }
        }
    }
}

?三種環(huán)境中,核心的執(zhí)行方法都是一樣的,采用反射來調用?具體事件的執(zhí)行方法,僅僅是方法所在的執(zhí)行環(huán)境不同而已。

到目前為止,我們的 EventBus 就完成了,通過注解的形式?實現事件的執(zhí)行方法,?通過注冊觀察者對象,生成? key 和 value ?建立關系存儲到集合中,?在事件發(fā)出的時候,?查找出對應的事件方法集合,然后在指定的執(zhí)行環(huán)境中調用。

有哪些寫的不對的地方請?zhí)岢觯視R上改正

我是 wanbo,一個會寫代碼的音樂人。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容