Kotlin中处理事件总线的通用监听器

5

我的目标是在Kotlin中实现一个非常简单的事件总线,不使用任何第三方库。我通过以下代码实现了这个目标。

class EventListener<T>(
    val owner: Any,
    val event: Class<T>,
    val callback: (T) -> Unit
)

interface IEventBus {
    fun <T> subscribe(owner: Any, event: Class<T>, callback: (T) -> Unit)
    fun unsubscribe(owner: Any)
    fun <T> push(event: T)
}

class EventBus : IEventBus {
    private val _listeners = mutableListOf<EventListener<*>>()

    override fun <T> subscribe(owner: Any, event: Class<T>, callback: (T) -> Unit) {
        val listener = EventListener(owner, event, callback)
        _listeners.add(listener)
    }

    override fun unsubscribe(owner: Any) {
        _listeners.removeAll {
            it.owner == owner
        }
    }

    override fun <T> push(event: T) {
        _listeners.forEach { listener ->
            try {
                val l = listener as EventListener<T> // is always a success
                l.callback(event)                    // throws an exception if can't handle the event
            } catch (ex: Exception) { }
        }
    }
}

然后使用方式如下:

// register listener
bus.subscribe(this, String::class.java) {
    print(it)
}
// push an event (from somewhere else in the project)
bus.push("Hello world!")

它能够正常工作,而且完全可用。但我对它不满意...将listener强制转换为EventListener总是会返回一些内容,如果l.callback(event)无法处理事件类型,它就会抛出异常。因此,如果有许多侦听器被订阅,它将产生许多不需要的异常,这些异常将被忽略。

我更喜欢首先进行某种检查,例如:

if (listener is EventListener<T>)
    listener.callback(event)

但我发现JVM在编译后会丢失有关泛型类型的信息。我还发现,使用Kotlin的inlinereified可以绕过这个问题,但是对于来自接口的方法,这些方法无法使用...

所以我的问题是,您是否知道处理这种泛型问题的更优雅的方法?

2个回答

2

既然您已经暴露了事件类 (EventListener#event),您可以使用 isInstance() 检查该类是否与您的事件实例兼容。

因此,不要写成:

if (listener is EventListener<T>)
    listener.callback(event)

你可以做以下事情:

if (listener.event.isInstance(event)) {
    // The cast is safe since you checked if the event can be received by the listener.
    (listener as EventListener<T>).callback(event)
}

基本类型

如果您想支持使用基本类型的 T,您可以将 Class<T> 更改为 KClass<T> 或在每个基本类型上手动检查实例(例如 event is Intevent is Long)。


然而,它似乎在 Int 方面存在问题。Int::class.java.isInstance(5) // false - Dawid Grajewski
1
@DawidGrajewski 这是因为 Kotlin 中的 Int 可以同时引用原始的 Java int 和封装的 Java Integer。如果您想支持原始类型,有两种可能性:将 Class<T> 替换为 KClass<T> 或检查 event 是否可分配给原始类型。例如 event is Intevent is Long。如果可以的话,我建议您将 Class 更改为 KClass,因为它将避免您对原始类型进行所有检查。 - Giorgio Antonioli
1
再次感谢!这对我来说已经结束了。祝你一切顺利:) - Dawid Grajewski

0

kotlinx.coroutines.flow.SharedFlow 的文档包含一个简单的示例:

SharedFlow 适用于将应用程序内发生的事件广播给可以随时加入和离开的订阅者。例如,以下类封装了一个事件总线,以会合方式将事件分发给所有订阅者,并暂停直到所有订阅者处理完每个事件:

class EventBus {
    private val _events = MutableSharedFlow<Event>() // private mutable shared flow
    val events = _events.asSharedFlow() // publicly exposed as read-only shared flow

    suspend fun produceEvent(event: Event) {
        _events.emit(event) // suspends until all subscribers receive it
    }
}

此外,相应的Android文档也很有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接