
前言
大家好,我是小彭。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 基礎:
- 1、Gradle 基礎 :Wrapper、Groovy、生命周期、Project、Task、增量
- 2、Gradle 插件:Plugin、Extension 擴展、NamedDomainObjectContainer、調試
- 3、Gradle 依賴管理
- 4、Maven 發(fā)布:SHAPSHOT 快照、uploadArchives、Nexus、AAR
- 5、Gradle 插件案例:EasyPrivacy、so 文件適配 64 位架構、ABI
二、AGP 插件:
- 1、AGP 構建過程
- 2、AGP 常用配置項:Manifest、BuildConfig、buildTypes、殼工程、環(huán)境切換
- 3、APG Transform:AOP、TransformTask、增量、字節(jié)碼、Dex
- 4、AGP 代碼混淆:ProGuard、R8、Optimize、Keep、組件化
- 5、APK 簽名:認證、完整性、v1、v2、v3、Zip、Wallet
- 6、AGP 案例:多渠道打包
三、組件化開發(fā):
- 1、方案積累:有贊、蘑菇街、得到、攜程、支付寶、手淘、愛奇藝、微信、美團
- 2、組件化架構基礎
- 3、ARouter 源碼分析
- 4、組件化案例:通用方案
- 5、組件化案例:組件化事件總線框架(本文)
- 6、組件化案例:組件化 Key-Value 框架
四、AOP 面向切面編程:
- 1、AOP 基礎
- 2、Java 注解
- 3、Java 注解處理器:APT、javac
- 4、Java 動態(tài)代理:代理模式、Proxy、字節(jié)碼
- 5、Java ServiceLoader:服務發(fā)現(xiàn)、SPI、META-INF
- 6、AspectJ 框架:Transform
- 7、Javassist 框架
- 8、ASM 框架
- 9、AspectJ 案例:限制按鈕點擊抖動
五、相關計算機基礎
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 事件 | ? | ? | ? | ? | ? | ? | ? |
| 移除事件 | ? | ? | ? | ? | ? | ? | ? |
| 線程調度 | ? | ? | ? | ? | ? | ? | ? |
| 跨進程 / 跨 App | ?(可支持) | ? | ? | ? | ? | ? | ? |
| 關鍵原理 | APT+靜態(tài)代理 | APT+動態(tài)代理 | APT+靜態(tài)代理 | LiveData | LiveData | APT | RxJava |
2.2 ModularEventBus 特性一覽
1、事件強約束
? 支持零配置快速使用;
? 支持 APT 注解處理器自動生成事件接口類;
? 支持編譯時合法性校驗和警告提示;
? 支持生成事件文檔;
? 支持增量編譯;
2、Lifecycle 生命周期感知
? 內置基于 LiveData 的 LiveDataBus;
? 支持自動取消訂閱,避免內存泄漏;
? 支持安全地發(fā)送事件與接收事件,避免產(chǎn)生空指針異?;虿槐匾男阅軗p耗;
? 支持永久訂閱事件;
? 支持自動清除沒有關聯(lián)訂閱者的空閑 LiveData 以釋放內存;
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 Project或Rebuild 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)的訂閱者時自動被清除(以釋放內存),默認值為 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 Project 或 Rebuild 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): 調試模式開關;
-
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;
- 支持替換內部 EventBus 工廠:ModularEventBus 已預設計事件總線工廠
IEventFactory,未來根據(jù)使用反饋考慮公開該 API; - 支持基于 Kotlin Flow 的 IEventFactory 工廠;
- 編譯時檢查在不同
@EventGroup中設置相同 modulaName 且相同eventName,但事件數(shù)據(jù)類型不同的異常。
6. 共同成長
- 歡迎提 Issue 幫助修復缺陷;
- 歡迎提 Pull Request 增加新的 Feature,讓 ModularEventBus 變得更加強大,你的 ID 會出現(xiàn)在 Contributors 中;
- 歡迎加 作者微信 與作者交流,歡迎加入交流群找到志同道合的伙伴
參考資料
- Android 消息總線的演進之路:用 LiveDataBus 替代 RxBus、EventBus —— 海亮(美團)著
- Android 組件化方案及組件消息總線 modular-event 實戰(zhàn) —— 海亮(美團)著