请点赞关注,你的支持对我意义重大。 🔥 Hi,我是小彭。本文已收录到 GitHub · AndroidFamily[1] 中。
大家好,我是小彭。2 年前,我们在 为了组件化改造学习十几家大厂的技术博客[2] 这篇文章里收集过各大厂的组件化方案。其中,有美团收银团队分享的组件化总线框架 modular-event
让我们印象深刻。然而,美团并未将该框架开源,我们只能望梅止渴。
在学习和借鉴美团 modular-event
方案中很多优秀的设计思想后,我亦发现方案中依然存在不一致风险和不足,故我决定对方案进行改进并向社区开源。项目主页为 Github · ModularEventBus[3],演示 Demo 可直接下载:Demo apk[4]。
欢迎提 Issue 帮助修复缺陷,欢迎提 Pull Request 增加新的 Feature,有用请点赞给 Star,给小彭一点创作的动力,谢谢。
事件总线框架最大的优点是 ”解耦“,即事件发布者与事件订阅者的解耦,事件的发布者不需要关心是否有人订阅该事件,也不需要关心是谁订阅该事件,代码耦合度较低。因此,事件总线框架更适合作为全局的事件通信方案,或者组件间通信的辅助方案。
然而,成也萧何败萧何。有人觉得事件总线好用,亦有人觉得事件总线不好用,归根结底还是因为事件总线太容易被滥用了,用时一时爽,维护火葬场。我将事件总线框架存在的问题概括为以下 5 种常见问题:
login_success
拼写为 login_succese
),那么订阅方将永远收不到事件。ModularEventBus 组件化事件总线框架的优点是:在保持发布者与订阅者的解耦的优势下,解决上述事件总线框架中存在的通病。 具体通过以下 5 个手段实现:
moduleName
,框架自动使用 "[moduleName]$$[eventName]"
作为最终的事件名(解决事件命名重复问题);modular-event 事件定义
ModularEventBus 是一款帮助 Android App 解决事件总线滥用问题的框架,亦可作为组件化基础设施。其解决方案是通过注解定义事件,由编译时 APT 注解处理器进行合法性检查和自动生成事件接口,以实现对事件定义、发布和订阅的强约束。
以下从多个维度对比常见的事件总线框架( ✅ 良好支持、✔️ 支持、❌ 不支持):
事件总线 | ModularEventBus | modular-event | SmartEventBus | LiveEventBus | LiveDataBus | EventBus | RxBus |
---|---|---|---|---|---|---|---|
开发者 | @彭旭锐 | @美团 | @JeremyLiao | @JeremyLiao | / | @greenrobot | / |
Github Star | 0 | 未开源 | 146 | 3.4k | / | 24.1k | / |
生成事件文档 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
空数据拦截 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
无数据事件 | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
泛型事件 | ✅ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
自动清除空闲事件 | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
事件强约束 | ✅ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ |
生命周期感知 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
延迟发送事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
有序接收事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
订阅 Sticky 事件 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
清除 Sticky 事件 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
移除事件 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
线程调度 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
跨进程 / 跨 App | ❌(可支持) | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
关键原理 | APT+静态代理 | APT+动态代理 | APT+静态代理 | LiveData | LiveData | APT | RxJava |
1、事件强约束
✅ 支持零配置快速使用;
✅ 支持 APT 注解处理器自动生成事件接口类;
✅ 支持编译时合法性校验和警告提示;
✅ 支持生成事件文档;
✅ 支持增量编译;
2、Lifecycle 生命周期感知
✅ 内置基于 LiveData
的 LiveDataBus;
✅ 支持自动取消订阅,避免内存泄漏;
✅ 支持安全地发送事件与接收事件,避免产生空指针异常或不必要的性能损耗;
✅ 支持永久订阅事件;
✅ 支持自动清除没有关联订阅者的空闲 LiveData
以释放内存;
3、更多特性支持
✅ 支持 Java / Kotlin;
✅ 支持 AndroidX;
✅ 支持订阅 Sticky 粘性事件,支持移除事件;
✅ 支持 Generic 泛型事件,如 List<String>
事件;
✅ 支持拦截空数据;
✅ 支持只发布事件不携带数据的无数据事件;
✅ 支持延迟发送事件;
✅ 支持有序接收事件。
模块级 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'
...
}
UserInfo.kt
data class UserInfo(val userName: String)
@EventGroup
注解修饰该接口:LoginEvents.kt
@EventGroup
interface LoginEvents {
// 事件名:login
// 事件数据类型:UserInfo
fun login(): UserInfo
// 事件名:logout
fun logout()
}
Make Project
或 Rebuild Project
等多种方式都可以触发注解处理器,处理器将根据事件定义自动生成相应的事件接口。例如,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));
}
}
EventDefineOfLoginEvents
事件类提供的静态方法订阅事件:订阅者示例
// 以生命周期感知模式订阅事件(不需要手动注销订阅)
EventDefineOfLoginEvents.login().observe(this) { value: UserInfo? ->
// Do something.
}
// 以永久模式订阅事件(需要手动注销订阅)
EventDefineOfLoginEvents.logout().observeForever { _: Void? ->
// Do something.
}
EventDefineOfLoginEvents
提供的静态方法发布事件:发布者示例
EventDefineOfLoginEvents.login().post(UserInfo("XIAOPENG"))
EventDefineOfLoginEvents.logout().post(null)
-dontwarn com.pengxr.modular.eventbus.generated.**
-keep class com.pengxr.modular.eventbus.generated.** { *; }
-keep @com.pengxr.modular.eventbus.facade.annotation.EventGroup class * {*;} # 可选
@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
事件且数据类型为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()
}
List<String>
),支持不携带数据的无数据事件。以下均为合法定义:Java 示例程序
// 事件数据类型为 String
String stringEventInJava();
// 事件数据类型为 List<String>
List<String> listEventInJava();
// 以下均视为无数据事件
void voidEventInJava1();
Void voidEventInJava2();
Kotlin 示例程序
// 事件数据类型为 String
fun stringEventInKotlin(): String
// 事件数据类型为 List<String>
fun listEventInKotlin(): List<String>
// 以下均视为无数据事件
fun voidEventInKotlin1()
fun voidEventInKotlin2(): Unit
fun voidEventInKotlin3(): Unit?
@Nullable
或 @NonNull
注解表示事件数据可空性,默认为可空类型。以下均为合法定义: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
@EventGroup
注解或 @Event
注解进行修改,以 @Event
的取值优先。示例程序
@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
事件且数据类型为String
类型。
在完成事件定义后,执行 Make Project
或 Rebuild Project
等多种方式都可以触发注解处理器,处理器将根据事件定义自动生成相应的事件接口。例如, 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
中的静态方法与 MainEvent
事件组中的每个事件一一对应,直接通过静态方法即可获取事件实例,而不再通过手动输入事件名字符串或事件数据类型,故可避免事件名错误或数据类型错误等问题。
所有的事件实例均是 IEvent
泛型接口的实现类,例如 open
事件属于 IEvent<String>
类型的事件实例。发布事件和订阅事件需要用到 IEvent
接口中定义的一系列 post 方法和 observe 方法,IEvent
接口的完整定义如下:
IEvent.kt
interface IEvent<T> {
/**
* 发布事件,允许在子线程发布
*/
@AnyThread
fun post(value: T?)
/**
* 延迟发布事件,允许在子线程发布
*/
@AnyThread
fun postDelay(value: T?, delay: Long)
/**
* 延迟发布事件,在准备发布前会检查 producer 处于活跃状态,允许在子线程发布
*
* @param producer 发布者的 LifecycleOwner
*/
@AnyThread
fun postDelay(value: T?, delay: Long, producer: LifecycleOwner)
/**
* 发布事件,允许在子线程发布,确保订阅者按照发布顺序接收事件
*/
@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?>)
/**
* 移除事件,关联的订阅者关系也会被解除
*/
@AnyThread
fun removeEvent()
}
使用 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)
使用 IEvent
接口定义的一系列 post()
接口发布事件,使用示例:
示例程序
// 发布事件,允许在子线程发布
EventDefineOfMainEvents.open().post("XIAO PENG")
// 延迟发布事件,允许在子线程发布
EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000)
// 延迟发布事件,在准备发布前会检查 producer 处于活跃状态,允许在子线程发布。
EventDefineOfMainEvents.open().postDelay("XIAO PENG", 5000, this)
// 发布事件,允许在子线程发布,确保订阅者按照发布顺序接收事件
EventDefineOfMainEvents.open().postOrderly("XIAO PENG")
// 移除事件
EventDefineOfMainEvents.open().removeEvent()
模块级 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
NullEventException
异常,在 release
模式默认为只拦截不抛出异常,在 debug
模式默认为拦截且抛出异常;示例程序
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")
}
})
IEventFactory
,未来根据使用反馈考虑公开该 API;@EventGroup
中设置相同 modulaName 且相同 eventName
,但事件数据类型不同的异常。我是小彭,带你构建 Android 知识体系。
[1]
GitHub · AndroidFamily: https://github.com/pengxurui/Android-NoteBook
[2]
为了组件化改造学习十几家大厂的技术博客: https://juejin.cn/post/6896099703474749453
[3]
Github · ModularEventBus: https://github.com/pengxurui/ModularEventBus
[4]
Demo apk: https://github.com/pengxurui/ModularEventBus/tree/main/demo
[5]
作者微信: https://github.com/pengxurui/AndroidFamily/blob/master/images/%E4%B8%AA%E4%BA%BA%E5%BE%AE%E4%BF%A1.jpeg
[6]
Android 消息总线的演进之路:用 LiveDataBus 替代 RxBus、EventBus: https://tech.meituan.com/2018/07/26/android-livedatabus.html
[7]
Android 组件化方案及组件消息总线 modular-event 实战: https://tech.meituan.com/2018/12/20/modular-event.html