【问题标题】:Handle generic listeners of event bus in Kotlin在 Kotlin 中处理事件总线的通用侦听器
【发布时间】:2019-10-23 07:36:32
【问题描述】:

我的目标是在没有任何第三方库的情况下在 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 将始终返回一些东西,然后 if l.callback(event) 无法处理会抛出异常的事件类型。因此,如果订阅了许多侦听器,则会生成许多不需要的异常,这些异常将被忽略。

我宁愿先做一些检查,比如:

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

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

所以我的问题是你知道处理这种通用问题的更优雅的方法吗?

【问题讨论】:

    标签: android generics events kotlin jvm


    【解决方案1】:

    由于您已经公开了事件的类 (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&lt;T&gt; 更改为 KClass&lt;T&gt; 或手动检查每个原始类型的实例(例如 event is Intevent is Long)。

    【讨论】:

    • 但是 Int 似乎有问题。 Int::class.java.isInstance(5) // 假
    • @DawidGrajewski 这是因为 Kotlin 中的 Int 既可以引用原始 Java int 也可以引用盒装 Java Integer。如果您想支持原语,您有两种可能性:将Class&lt;T&gt; 替换为KClass&lt;T&gt; 或检查event 是否可分配给原语类型。例如。 event is Intevent is Long。如果可以的话,显然我建议您将Class 更改为KClass,因为它会避免您对原始类型进行所有检查。
    • 再次感谢您!这对我来说关闭了整个事情。祝你好运:)
    【解决方案2】:

    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
        }
    }
    

    此外,corresponding Android documentation 也很有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-11
      • 1970-01-01
      • 2017-12-16
      • 1970-01-01
      • 2018-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多