Android 架构Android Other读书点点

美团组件化事件总线方案改进:ModularEventBus

2022-08-30  本文已影响0人  彭旭锐

前言

大家好,我是小彭。2 年前,我们在 为了组件化改造学习十几家大厂的技术博客 这篇文章里收集过各大厂的组件化方案。其中,有美团收银团队分享的组件化总线框架 modular-event 让我们印象深刻。然而,美团并未将该框架开源,我们只能望梅止渴。

在学习和借鉴美团 modular-event 方案中很多优秀的设计思想后,我亦发现方案中依然存在不一致风险和不足,故我决定对方案进行改进并向社区开源。项目主页为 Github · ModularEventBus,演示 Demo 可直接下载:
Demo apk

欢迎提 Issue 帮助修复缺陷,欢迎提 Pull Request 增加新的 Feature,有用请点赞给 Star,给小彭一点创作的动力,谢谢。


这篇文章是 组件化系列文章第 5 篇,相关 Android 工程化专栏完整文章列表:

一、Gradle 基础:

二、AGP 插件:

三、组件化开发:

四、AOP 面向切面编程:

五、相关计算机基础


1. 认识事件总线

1.1 事件总线的优点

事件总线框架最大的优点是 ”解耦“,即事件发布者与事件订阅者的解耦,事件的发布者不需要关心是否有人订阅该事件,也不需要关心是谁订阅该事件,代码耦合度较低。因此,事件总线框架更适合作为全局的事件通信方案,或者组件间通信的辅助方案。

1.2 事件总线的缺点

然而,成也萧何败萧何。有人觉得事件总线好用,亦有人觉得事件总线不好用,归根结底还是因为事件总线太容易被滥用了,用时一时爽,维护火葬场。我将事件总线框架存在的问题概括为以下 5 种常见问题:

1.3 ModularEventBus 的解决方案

ModularEventBus 组件化事件总线框架的优点是: 在保持发布者与订阅者的解耦的优势下,解决上述事件总线框架中存在的通病。 具体通过以下 5 个手段实现:

1.4 与美团 modular-event 对比有哪些什么不同?


2. ModularEventBus 能做什么?

ModularEventBus 是一款帮助 Android App 解决事件总线滥用问题的框架,亦可作为组件化基础设施。 其解决方案是通过注解定义事件,由编译时 APT 注解处理器进行合法性检查和自动生成事件接口,以实现对事件定义、发布和订阅的强约束。

2.1 常见事件总线框架对比

以下从多个维度对比常见的事件总线框架( ✅ 良好支持、✔️ 支持、❌ 不支持):

事件总线 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

2.2 ModularEventBus 特性一览

1、事件强约束

✅ 支持零配置快速使用;

✅ 支持 APT 注解处理器自动生成事件接口类;

✅ 支持编译时合法性校验和警告提示;

✅ 支持生成事件文档;

✅ 支持增量编译;

2、Lifecycle 生命周期感知

✅ 内置基于 LiveData 的 LiveDataBus;

✅ 支持自动取消订阅,避免内存泄漏;

✅ 支持安全地发送事件与接收事件,避免产生空指针异常或不必要的性能损耗;

✅ 支持永久订阅事件;

✅ 支持自动清除没有关联订阅者的空闲 LiveData 以释放内存;

3、更多特性支持

✅ 支持 Java / Kotlin;

✅ 支持 AndroidX;

✅ 支持订阅 Sticky 粘性事件,支持移除事件;

✅ 支持 Generic 泛型事件,如 List<String> 事件;

✅ 支持拦截空数据;

✅ 支持只发布事件不携带数据的无数据事件;

✅ 支持延迟发送事件;

✅ 支持有序接收事件。


3. ModularEventBus 快速使用

模块级 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)

LoginEvents.kt

@EventGroup
interface LoginEvents {

  // 事件名:login
  // 事件数据类型:UserInfo
  fun login(): UserInfo

  // 事件名:logout
  fun logout()
}

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.login().observe(this) { value: UserInfo? ->
    // Do something.
}

// 以永久模式订阅事件(需要手动注销订阅)
EventDefineOfLoginEvents.logout().observeForever { _: Void? ->
    // Do something.
}

发布者示例

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 * {*;} # 可选

4. 完整使用文档

4.1 定义事件

模板程序如下:

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
@EventGroup
interface IgnoreEvent {

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

    fun method()
}

示例程序

// 虽然过时,但依然是有效的事件定义
@Deprecated("Don't use it.")
@EventGroup
interface DeprecatedEvent {

    @Deprecated("Don't use it.")
    fun deprecatedMethod()
}

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?

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(autoClear = true)
interface MainEvents {

    @Event(autoClear = false)
    fun normalEvent(): String
    
    // 继承 @EventGroup 中的 autoClear 取值
    fun autoClearEvent(): String
}

示例程序

com.pengxr.sample.events.MainEvents.kt

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

    fun open(): String
}

提示: 以上即定义了一个 MainEvents 事件组,其中包含一个 main$$open 事件且数据类型为 String 类型。

4.2 执行注解处理器

在完成事件定义后,执行 Make ProjectRebuild 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()
}

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 发布事件

使用 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()

4.5 更多功能

模块级 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

示例程序

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. 未来功能规划


6. 共同成长

参考资料

上一篇下一篇

猜你喜欢

热点阅读