美團組件化事件總線方案改進:ModularEventBus

前言

大家好羊精,我是小彭。2 年前浪蹂,我們在 為了組件化改造學習十幾家大廠的技術博客 這篇文章里收集過各大廠的組件化方案吼拥。其中蕾管,有美團收銀團隊分享的組件化總線框架 modular-event 讓我們印象深刻。然而啡氢,美團并未將該框架開源状囱,我們只能望梅止渴。

在學習和借鑒美團 modular-event 方案中很多優(yōu)秀的設計思想后倘是,我亦發(fā)現(xiàn)方案中依然存在不一致風險和不足亭枷,故我決定對方案進行改進并向社區(qū)開源。項目主頁為 Github · ModularEventBus搀崭,演示 Demo 可直接下載:
Demo apk叨粘。

歡迎提 Issue 幫助修復缺陷,歡迎提 Pull Request 增加新的 Feature,有用請點贊給 Star升敲,給小彭一點創(chuàng)作的動力答倡,謝謝。


這篇文章是 組件化系列文章第 5 篇驴党,相關 Android 工程化專欄完整文章列表:

一瘪撇、Gradle 基礎:

二、AGP 插件:

三、組件化開發(fā):

四渣锦、AOP 面向切面編程:

五蝎困、相關計算機基礎


1. 認識事件總線

1.1 事件總線的優(yōu)點

事件總線框架最大的優(yōu)點是 ”解耦“伍派,即事件發(fā)布者與事件訂閱者的解耦,事件的發(fā)布者不需要關心是否有人訂閱該事件剩胁,也不需要關心是誰訂閱該事件诉植,代碼耦合度較低。因此摧冀,事件總線框架更適合作為全局的事件通信方案倍踪,或者組件間通信的輔助方案。

1.2 事件總線的缺點

然而索昂,成也蕭何敗蕭何建车。有人覺得事件總線好用,亦有人覺得事件總線不好用椒惨,歸根結底還是因為事件總線太容易被濫用了缤至,用時一時爽,維護火葬場康谆。我將事件總線框架存在的問題概括為以下 5 種常見問題:

  • 1领斥、消息難溯源: 在閱讀源碼的過程中,如果需要查找發(fā)布事件或訂閱事件的地方沃暗,只能通過查找事件引用的方式進行溯源月洛,增大了理解代碼邏輯的難度。特別是當項目中到處是臨時事件時孽锥,難度會大大增加嚼黔;

  • 2、臨時事件濫用: 由于框架對事件定義沒有強制約束惜辑,開發(fā)者可以隨意地在項目的各個角落定義事件唬涧。導致整個項目都是臨時事件飛來飛去,增大后期維護的難度盛撑;

  • 3碎节、數(shù)據(jù)類型轉換錯誤: LiveDataBus 等事件總線框架需要開發(fā)者手動輸入事件數(shù)據(jù)類型,當訂閱方與發(fā)送方使用不同的數(shù)據(jù)類型時抵卫,會發(fā)生類型轉換錯誤狮荔。在發(fā)生事件命名沖突時胎撇,出錯的概率會大大增加,存在隱患殖氏;

  • 4创坞、事件命名重復: 由于框架對事件命名沒有強制約束,不同組件有可能定義重名的事件受葛,產(chǎn)生邏輯錯誤题涨。如果重名的事件還使用了不同的數(shù)據(jù)類型,還會出現(xiàn)類型轉換錯誤总滩,存在隱患纲堵;

  • 5、事件命名疏忽: 與 ”事件命名重復“ 類似闰渔,由于框架對事件命名沒有檢查席函,有可能出現(xiàn)開發(fā)者復制粘貼后忘記修改事件變量值的問題,或者變量值拼寫錯誤(例如 login_success 拼寫為 login_succese)冈涧,那么訂閱方將永遠收不到事件茂附。

1.3 ModularEventBus 的解決方案

ModularEventBus 組件化事件總線框架的優(yōu)點是: 在保持發(fā)布者與訂閱者的解耦的優(yōu)勢下,解決上述事件總線框架中存在的通病督弓。 具體通過以下 5 個手段實現(xiàn):

  • 1营曼、事件聲明聚合: 發(fā)布者和訂閱者只能使用預定義的事件,嚴格禁止使用臨時事件愚隧,事件需要按照約定聚合定義在一個文件中(解決臨時事件濫用問題)蒂阱;

  • 2、區(qū)分不同組件的同名事件: 在定義事件時需要指定事件所屬 moduleName狂塘,框架自動使用 "[moduleName]$$[eventName]" 作為最終的事件名(解決事件命名重復問題)录煤;

  • 3、事件數(shù)據(jù)類型聲明: 在定義事件時需要指定事件的數(shù)據(jù)類型荞胡,框架自動使用該數(shù)據(jù)類型發(fā)送和訂閱事件(解決數(shù)據(jù)類型轉換錯誤問題)妈踊;

  • 4、接口強約束: 運行時使用事件類發(fā)布和訂閱事件泪漂,框架自動使用事件定義的事件名和數(shù)據(jù)類型廊营,而不需要手動輸入事件名和數(shù)據(jù)類型(解決事件命名命名錯誤);

  • 5窖梁、APT 生成接口類: 框架在編譯時使用 APT 注解處理器自動生成事件接口類赘风。

1.4 與美團 modular-event 對比有哪些什么不同夹囚?

  • modular-event 使用靜態(tài)常量定義事件纵刘,為什么 ModularEventBus 用接口定義事件?

    美團 modular-event 使用常量引入了重復信息荸哟,存在不一致風險假哎。例如開發(fā)者復制一行常量后瞬捕,只修改常量名但忘記修改值,這種錯誤往往很難被發(fā)現(xiàn)舵抹。而 ModularEventBus 使用方法名作為事件名肪虎,方法返回值作為事件數(shù)據(jù)類型,不會引入重復信息且更加簡潔惧蛹。

  • modular-event 使用動態(tài)代理扇救,為什么 ModularEventBus 不需要?

    美團 modular-event 使用動態(tài)代理 API 統(tǒng)一接管了事件的發(fā)布和訂閱香嗓,但考慮到這部分代理邏輯非常簡單(獲取事件名并交給 LiveDataBus 完成后續(xù)的發(fā)布和訂閱邏輯)迅腔,且框架本身已經(jīng)引入了編譯時 APT 技術,完全可以在編譯時生成這部分代理邏輯靠娱,沒必要使用動態(tài)代理 API沧烈。

  • 更多特性支持:

    此外 ModularEventBus 還支持生成事件文檔、空數(shù)據(jù)攔截像云、泛型事件锌雀、自動清除空閑事件等特性。


2. ModularEventBus 能做什么迅诬?

ModularEventBus 是一款幫助 Android App 解決事件總線濫用問題的框架腋逆,亦可作為組件化基礎設施。 其解決方案是通過注解定義事件侈贷,由編譯時 APT 注解處理器進行合法性檢查和自動生成事件接口闲礼,以實現(xiàn)對事件定義、發(fā)布和訂閱的強約束铐维。

2.1 常見事件總線框架對比

以下從多個維度對比常見的事件總線框架( ? 良好支持柬泽、?? 支持、? 不支持):

事件總線 ModularEventBus modular-event SmartEventBus LiveEventBus LiveDataBus EventBus RxBus
開發(fā)者 @彭旭銳 @美團 @JeremyLiao @JeremyLiao / @greenrobot /
Github Star 0 未開源 146 3.4k / 24.1k /
生成事件文檔 ? ? ? ? ? ? ?
空數(shù)據(jù)攔截 ? ? ? ? ? ? ?
無數(shù)據(jù)事件 ?? ? ? ? ? ? ?
泛型事件 ? ? ?? ?? ? ? ?
自動清除空閑事件 ? ? ? ? ? ? ?
事件強約束 ? ?? ?? ? ? ? ?
生命周期感知 ? ? ? ? ? ? ?
延遲發(fā)送事件 ? ? ? ? ? ? ?
有序接收事件 ? ? ? ? ? ? ?
訂閱 Sticky 事件 ? ? ? ? ? ? ?
清除 Sticky 事件 ? ? ? ? ? ? ?
移除事件 ? ? ? ? ? ? ?
線程調(diào)度 ? ? ? ? ? ? ?
跨進程 / 跨 App ?(可支持) ? ? ? ? ? ?
關鍵原理 APT+靜態(tài)代理 APT+動態(tài)代理 APT+靜態(tài)代理 LiveData LiveData APT RxJava

2.2 ModularEventBus 特性一覽

1嫁蛇、事件強約束

? 支持零配置快速使用锨并;

? 支持 APT 注解處理器自動生成事件接口類;

? 支持編譯時合法性校驗和警告提示睬棚;

? 支持生成事件文檔第煮;

? 支持增量編譯;

2抑党、Lifecycle 生命周期感知

? 內(nèi)置基于 LiveData 的 LiveDataBus包警;

? 支持自動取消訂閱,避免內(nèi)存泄漏底靠;

? 支持安全地發(fā)送事件與接收事件害晦,避免產(chǎn)生空指針異常或不必要的性能損耗暑中;

? 支持永久訂閱事件壹瘟;

? 支持自動清除沒有關聯(lián)訂閱者的空閑 LiveData 以釋放內(nèi)存鲫剿;

3、更多特性支持

? 支持 Java / Kotlin稻轨;

? 支持 AndroidX灵莲;

? 支持訂閱 Sticky 粘性事件,支持移除事件殴俱;

? 支持 Generic 泛型事件政冻,如 List<String> 事件;

? 支持攔截空數(shù)據(jù)线欲;

? 支持只發(fā)布事件不攜帶數(shù)據(jù)的無數(shù)據(jù)事件赠幕;

? 支持延遲發(fā)送事件;

? 支持有序接收事件询筏。


3. ModularEventBus 快速使用

  • 1榕堰、添加依賴

模塊級 build.gradle

plugins {
    id 'com.android.application' // 或 id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}
dependencies {
    // 替換成最新版本
    implementation 'io.github.pengxurui:modular-eventbus-api:1.0.4'
    kapt 'io.github.pengxurui:modular-eventbus-compiler:1.0.4'
    ...
}
  • 2、定義事件數(shù)據(jù)類型(可選): 定義事件關聯(lián)的數(shù)據(jù)類型嫌套,對于只發(fā)布事件而不需要攜帶數(shù)據(jù)的場景逆屡,可以不定義事件類型。

UserInfo.kt

data class UserInfo(val userName: String)
  • 3踱讨、定義事件: 使用接口定義事件名和事件數(shù)據(jù)類型魏蔗,并使用 @EventGroup 注解修飾該接口:

LoginEvents.kt

@EventGroup
interface LoginEvents {

  // 事件名:login
  // 事件數(shù)據(jù)類型:UserInfo
  fun login(): UserInfo

  // 事件名:logout
  fun logout()
}
  • 4、執(zhí)行注解處理器: 執(zhí)行 Make ProjectRebuild Project 等多種方式都可以觸發(fā)注解處理器痹筛,處理器將根據(jù)事件定義自動生成相應的事件接口莺治。例如,LoginEvents 對應的事件類為:

EventDefineOfLoginEvents.java

/**
 * Auto generate code, do not modify!!!
 * @see com.pengxr.sampleloginlib.events.LoginEvents 
 */
@SuppressWarnings("unchecked")
public class EventDefineOfLoginEvents implements IEventGroup {
    private EventDefineOfLoginEvents() {
    }

    public static IEvent<UserInfo> login() {
        return (IEvent<UserInfo>) (ModularEventBus.INSTANCE.createObservable("com.pengxr.sampleloginlib.events.LoginEvents$$login", UserInfo.class, false, true));
    }

    public static IEvent<Void> logout() {
        return (IEvent<Void>) (ModularEventBus.INSTANCE.createObservable("com.pengxr.sampleloginlib.events.LoginEvents$$logout", Void.class, true, false));
    }
}
  • 5帚稠、訂閱事件: 使用 EventDefineOfLoginEvents 事件類提供的靜態(tài)方法訂閱事件:

訂閱者示例

// 以生命周期感知模式訂閱事件(不需要手動注銷訂閱)
EventDefineOfLoginEvents.login().observe(this) { value: UserInfo? ->
    // Do something.
}

// 以永久模式訂閱事件(需要手動注銷訂閱)
EventDefineOfLoginEvents.logout().observeForever { _: Void? ->
    // Do something.
}
  • 6谣旁、發(fā)布事件: 使用 EventDefineOfLoginEvents 提供的靜態(tài)方法發(fā)布事件:

發(fā)布者示例

EventDefineOfLoginEvents.login().post(UserInfo("XIAOPENG"))

EventDefineOfLoginEvents.logout().post(null)
  • 7、添加混淆規(guī)則(如果使用了 minifyEnabled true):
-dontwarn com.pengxr.modular.eventbus.generated.**
-keep class com.pengxr.modular.eventbus.generated.** { *; }
-keep @com.pengxr.modular.eventbus.facade.annotation.EventGroup class * {*;} # 可選

4. 完整使用文檔

4.1 定義事件

  • 使用注解定義事件:

    • @EventGroup 注解: @EventGroup 注解用于定義事件組滋早,修飾于 interface 接口上榄审,在該類中定義的每個方法均視為一個事件定義;

    • @Event 注解: @Event 注解用于事件組中的事件定義杆麸,亦可省略搁进。

模板程序如下:

com.pengxr.sample.events.MainEvents.kt

// 事件組
@EventGroup
interface MainEvents {

    // 事件
    // @Event 可以省略
    @Event
    fun open(): String
}

提示: 以上即定義了一個 MainEvents 事件組,其中包含一個 com.pengxr.sample.events.MainEvents$$open 事件且數(shù)據(jù)類型為 String 類型昔头。

亦兼容將 @EventGroup 修飾于 class 類而非 interface 接口饼问,但會有編譯時警告: Annotated @EventGroup on a class type [IllegalEvent], expected a interface. Is that really what you want?

錯誤示例

@EventGroup
class IllegalEvent {

    fun illegalEvent() {

    }
}
  • 使用 @Ignore 注解忽略定義: 使用 @Ignore 注解可以排除事件類或事件方法揭斧,使其不被視為事件定義莱革。

示例程序

// 可以修飾于事件組
@Ignore
@EventGroup
interface IgnoreEvent {

    // 亦可修飾于事件
    @Ignore
    fun ignoredMethod()

    fun method()
}
  • 使用 @Deprecated 注解提示過時: 使用 @Deprecated 注解可以標記事件為過時。與 @Ignore 不同是,@Deprecated 修飾的類或方法依然是有效的事件定義驮吱。

示例程序

// 雖然過時,但依然是有效的事件定義
@Deprecated("Don't use it.")
@EventGroup
interface DeprecatedEvent {

    @Deprecated("Don't use it.")
    fun deprecatedMethod()
}
  • 定義事件數(shù)據(jù)類型: 事件方法返回值即表示事件數(shù)據(jù)類型萧吠,支持泛型(如 List<String>)左冬,支持不攜帶數(shù)據(jù)的無數(shù)據(jù)事件。以下均為合法定義:

Java 示例程序

// 事件數(shù)據(jù)類型為 String
String stringEventInJava();

// 事件數(shù)據(jù)類型為 List<String>
List<String> listEventInJava();

// 以下均視為無數(shù)據(jù)事件
void voidEventInJava1();
Void voidEventInJava2();

Kotlin 示例程序

// 事件數(shù)據(jù)類型為 String
fun stringEventInKotlin(): String

// 事件數(shù)據(jù)類型為 List<String>
fun listEventInKotlin(): List<String>

// 以下均視為無數(shù)據(jù)事件
fun voidEventInKotlin1()
fun voidEventInKotlin2(): Unit
fun voidEventInKotlin3(): Unit?
  • 定義事件數(shù)據(jù)可空性: 使用 @Nullable@NonNull 注解表示事件數(shù)據(jù)可空性纸型,默認為可空類型拇砰。以下均為合法定義:

Java 示例程序

@NonNull
String nonNullEventInJava();

@Nullable
String nullableEventInJava();

// 默認視為 @Nullable
String eventInJava();

Kotlin 示例程序

fun nonNullEventInKotlin(): String

// 提示:Kotlin 編譯器將返回類型上的 ? 號視為 @org.jetbrains.annotations.Nullable
fun nullableEventInKotlin(): String?

以下為支持的可空性注解:

org.jetbrains.annotations.Nullable
android.annotation.Nullable
androidx.annotation.Nullable

org.jetbrains.annotations.NotNull
android.annotation.NonNull
androidx.annotation.NonNull
  • 定義自動清除事件: 支持配置在事件沒有關聯(lián)的訂閱者時自動被清除(以釋放內(nèi)存),默認值為 false狰腌〕疲可以使用 @EventGroup 注解或 @Event 注解進行修改,以 @Event 的取值優(yōu)先琼腔。

示例程序

@EventGroup(autoClear = true)
interface MainEvents {

    @Event(autoClear = false)
    fun normalEvent(): String
    
    // 繼承 @EventGroup 中的 autoClear 取值
    fun autoClearEvent(): String
}
  • 定義事件所屬組件名: 為避免不同組件中的事件名重復瑰枫,框架自動使用 "[moduleName]$$[eventName]" 作為最終的事件名。默認使用事件組的 [全限定類名] 作為 moduleName丹莲,可以使用 @EventGroup 注解進行修改光坝。

示例程序

com.pengxr.sample.events.MainEvents.kt

@EventGroup(moduleName = "main")
interface MainEvents {

    fun open(): String
}

提示: 以上即定義了一個 MainEvents 事件組,其中包含一個 main$$open 事件且數(shù)據(jù)類型為 String 類型甥材。

4.2 執(zhí)行注解處理器

在完成事件定義后盯另,執(zhí)行 Make ProjectRebuild Project 等多種方式都可以觸發(fā)注解處理器,處理器將根據(jù)事件定義自動生成相應的事件接口洲赵。例如鸳惯, MainEvents 對應的事件接口為:

com.pengxr.modular.eventbus.generated.events.com.pengxr.sample.events.EventDefineOfMainEvents.java

/**
 * Auto generate code, do not modify!!!
 * @see com.pengxr.sample.events.MainEvents 
 */
@SuppressWarnings("unchecked")
public class EventDefineOfMainEvents implements IEventGroup {
    private EventDefineOfMainEvents() {
    }

    public static IEvent<String> open() {
        return (IEvent<String>) (ModularEventBus.INSTANCE.createObservable("main$$open", String.class, false, false));
    }
}

EventDefineOfMainEvents 中的靜態(tài)方法與 MainEvent 事件組中的每個事件一一對應,直接通過靜態(tài)方法即可獲取事件實例叠萍,而不再通過手動輸入事件名字符串或事件數(shù)據(jù)類型芝发,故可避免事件名錯誤或數(shù)據(jù)類型錯誤等問題。

所有的事件實例均是 IEvent 泛型接口的實現(xiàn)類苛谷,例如 open 事件屬于 IEvent<String> 類型的事件實例后德。發(fā)布事件和訂閱事件需要用到 IEvent 接口中定義的一系列 post 方法和 observe 方法,IEvent 接口的完整定義如下:

IEvent.kt

interface IEvent<T> {

    /**
     * 發(fā)布事件抄腔,允許在子線程發(fā)布
     */
    @AnyThread
    fun post(value: T?)

    /**
     * 延遲發(fā)布事件瓢湃,允許在子線程發(fā)布
     */
    @AnyThread
    fun postDelay(value: T?, delay: Long)

    /**
     * 延遲發(fā)布事件,在準備發(fā)布前會檢查 producer 處于活躍狀態(tài)赫蛇,允許在子線程發(fā)布
     *
     * @param producer 發(fā)布者的 LifecycleOwner
     */
    @AnyThread
    fun postDelay(value: T?, delay: Long, producer: LifecycleOwner)

    /**
     * 發(fā)布事件绵患,允許在子線程發(fā)布,確保訂閱者按照發(fā)布順序接收事件
     */
    @AnyThread
    fun postOrderly(value: T?)

    /**
     * 以生命周期感知模式訂閱事件(不需要手動注銷訂閱)
     */
    @AnyThread
    fun observe(consumer: LifecycleOwner, observer: Observer<T?>)

    /**
     * 以生命周期感知模式粘性訂閱事件(不需要手動注銷訂閱)
     */
    @AnyThread
    fun observeSticky(consumer: LifecycleOwner, observer: Observer<T?>)
    
    /**
     * 以永久模式訂閱事件(需要手動注銷訂閱)
     */
    fun observeForever(observer: Observer<T?>)

    /**
     * 以永久模式粘性訂閱事件(需要手動注銷訂閱)
     *
     * @param observer Event observer.
     */
    @AnyThread
    fun observeStickyForever(observer: Observer<T?>)

    /**
     * 注銷訂閱者
     */
    @AnyThread
    fun removeObserver(observer: Observer<T?>)

    /**
     * 移除事件悟耘,關聯(lián)的訂閱者關系也會被解除
     */
    @AnyThread
    fun removeEvent()
}

4.3 訂閱事件

使用 IEvent 接口定義的一系列 observe() 接口訂閱事件落蝙,使用示例:

示例程序

// 以生命周期感知模式訂閱(不需要手動注銷訂閱)
EventDefineOfMainEvents.open().observe(this) {
    // do something.
}

// 以生命周期感知模式、且粘性模式訂閱(不需要手動注銷訂閱)
EventDefineOfMainEvents.open().observeSticky(this) {
    // do something.
}

val foreverObserver = Observer<String?> {
    // do something.
}

// 以永久模式訂閱(需要手動注銷訂閱)
EventDefineOfMainEvents.open().observeForever(foreverObserver)

// 以永久模式,且粘性模式訂閱(需要手動注銷訂閱)
EventDefineOfMainEvents.open().observeStickyForever(foreverObserver)

// 移除觀察者
EventDefineOfMainEvents.open().removeObserver(foreverObserver)

4.4 發(fā)布事件

使用 IEvent 接口定義的一系列 post() 接口發(fā)布事件筏勒,使用示例:

示例程序

// 發(fā)布事件移迫,允許在子線程發(fā)布
EventDefineOfMainEvents.open().post("XIAO PENG")

// 延遲發(fā)布事件,允許在子線程發(fā)布
EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000)

// 延遲發(fā)布事件管行,在準備發(fā)布前會檢查 producer 處于活躍狀態(tài)厨埋,允許在子線程發(fā)布。
EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000, this)

// 發(fā)布事件捐顷,允許在子線程發(fā)布荡陷,確保訂閱者按照發(fā)布順序接收事件
EventDefineOfMainEvents.open().postOrderly("XIAO PENG")
  
// 移除事件
EventDefineOfMainEvents.open().removeEvent()

4.5 更多功能

  • 生成事件文檔(可選): 支持生成事件文檔,需要在 Gradle 配置中開啟:

模塊級 build.gradle

// 需要生成事件文檔的模塊就增加配置:
android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    MODULAR_EVENTBUS_GENERATE_DOC: "enable",
                    MODULAR_EVENTBUS_MODULE_NAME : project.getName()
                ]
            }
        }
    }
}

文檔生成路徑: build/generated/source/kapt/[buildType]/com/pengxr/modular/eventbus/generated/docs/eventgroup-of-[MODULAR_EVENTBUS_MODULE_NAME].json

  • 配置(可選):
    • debug(Boolean): 調(diào)試模式開關迅涮;
    • throwNullEventException(Boolean): 非空事件發(fā)布空數(shù)據(jù)時是否拋出 NullEventException 異常废赞,在 release 模式默認為只攔截不拋出異常,在 debug 模式默認為攔截且拋出異常叮姑;
    • setEventListener(IEventListener): 全局監(jiān)聽接口唉地。

示例程序

ModularEventBus.debug(true)
    .throwNullEventException(true)
    .setEventListener(object : IEventListener {
        override fun <T> onEventPost(eventName: String, event: BaseEvent<T>, data: T?) {
            Log.i(TAG, "onEventPost: $eventName, event = $event, data = $data")
        }
    })

5. 未來功能規(guī)劃

  • 支持跨進程 / 跨 App:LiveEventBus 框架支持跨進程 / 跨 App,未來根據(jù)使用反饋考慮實現(xiàn)該 Feature传透;
  • 支持替換內(nèi)部 EventBus 工廠:ModularEventBus 已預設計事件總線工廠 IEventFactory渣蜗,未來根據(jù)使用反饋考慮公開該 API;
  • 支持基于 Kotlin Flow 的 IEventFactory 工廠旷祸;
  • 編譯時檢查在不同 @EventGroup 中設置相同 modulaName 且相同 eventName耕拷,但事件數(shù)據(jù)類型不同的異常。

6. 共同成長

  • 歡迎提 Issue 幫助修復缺陷托享;
  • 歡迎提 Pull Request 增加新的 Feature骚烧,讓 ModularEventBus 變得更加強大,你的 ID 會出現(xiàn)在 Contributors 中闰围;
  • 歡迎加 作者微信 與作者交流赃绊,歡迎加入交流群找到志同道合的伙伴

參考資料

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羡榴,一起剝皮案震驚了整個濱河市碧查,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌校仑,老刑警劉巖忠售,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異迄沫,居然都是意外死亡稻扬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門羊瘩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泰佳,“玉大人盼砍,你說我怎么就攤上這事∈潘” “怎么了浇坐?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黔宛。 經(jīng)常有香客問我近刘,道長,這世上最難降的妖魔是什么宁昭? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任跌宛,我火速辦了婚禮酗宋,結果婚禮上积仗,老公的妹妹穿的比我還像新娘。我一直安慰自己蜕猫,他們只是感情好寂曹,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著回右,像睡著了一般隆圆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上翔烁,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天渺氧,我揣著相機與錄音,去河邊找鬼蹬屹。 笑死侣背,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的慨默。 我是一名探鬼主播贩耐,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厦取!你這毒婦竟也來了潮太?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤虾攻,失蹤者是張志新(化名)和其女友劉穎铡买,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霎箍,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡寻狂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了朋沮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛇券。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缀壤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纠亚,到底是詐尸還是另有隱情塘慕,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布蒂胞,位于F島的核電站图呢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏骗随。R本人自食惡果不足惜蛤织,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸿染。 院中可真熱鬧指蚜,春花似錦、人聲如沸涨椒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚕冬。三九已至免猾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囤热,已是汗流浹背猎提。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旁蔼,地道東北人锨苏。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像牌芋,于是被迫代替她去往敵國和親蚓炬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容