当前位置: 首页 > news >正文

AndroidEventBus 发布者发布一次订阅者接收到多次问题

在使用 AndroidEventBus 时,可能会遇到一个异常现象:发布者仅发送一次事件,订阅者却接收到多次。本文将分析这一问题的原因,并提供解决方案。

先说结论和解决措施

AndroidEventBus 对内置数据结构(如 Map 接口下的实现类如 LinkedHashMap 等)的重复接收问题,根源是:

  1. 内置数据结构有复杂的继承和接口实现链;

  2. EventBus 内部的 addInterfaces 方法的去重逻辑错误(用 EventType 列表与 Class 对象比较),导致生成重复的 EventType。eventTypes.contains(interfaceClass)) 结果永远是 false,因为 eventTypes 是 List< EventType > 类型,而 interfaceClass 是 Class 类型,无法比较

  3. 遍历 EventType 列表查找与订阅者接收类型时发现有多个匹配项,导致多个事件分发

解决措施: 使用自定义事件类(也就是事件的传播载体),而不是使用内置的数据结构,例如 Map、List 接口的实现类等

问题现象与错误代码示例

当使用内置数据结构(如 Map、List 的实现类等)作为事件载体时,容易出现该问题。以下是具体代码示例:

发布者代码

val textAndPriority: MutableMap<String, Any> = mutableMapOf() // 也就是 Java 中的LinkedHashMap
textAndPriority["text"] = "Hello EventBus"
textAndPriority["priority"] = -1Log.d(TAG, "Post Map event")
addLog("发送 Map 事件: $textAndPriority")// 发布消息(使用 LinkedHashMap 作为事件载体)
EventBus.getDefault().post(textAndPriority, GUIDE_AUDIO_TEXT)

订阅者代码

@Subscriber(tag = GUIDE_AUDIO_TEXT)
fun onSubscribe(textAndPriority: Map<String, Any?>) { // 使用 Map 接收Log.d(TAG, "Received Map event: $textAndPriority")mainHandler.post {updateStatus("已接收 EventBus 事件")addLog("接收 Map 事件: $textAndPriority")}
}

执行日志
发布者发送一次事件后,订阅者日志显示接收了三次相同事件:

接收 Map 事件: {text=Hello EventBus,priority=-1}
接收 Map 事件: {text=Hello EventBus,priority=-1}
接收 Map 事件: {text=Hello EventBus,priority=-1}

解决方案

使用自定义事件类作为事件载体,而非内置数据结构(如 Map、List 的实现类等)。

例如,定义一个包含相同数据的自定义类:

data class CustomEvent(val text: String, val priority: Int)

发布时使用该类:

val event = CustomEvent("Hello EventBus", -1)
EventBus.getDefault().post(event, GUIDE_AUDIO_TEXT)

订阅时接收该类:

@Subscriber(tag = GUIDE_AUDIO_TEXT)
fun onSubscribe(event: CustomEvent) {// 处理事件
}

原因分析(结合源码)

要理解为什么内置数据结构会导致重复接收,需从 AndroidEventBus 的事件分发逻辑入手

1. 事件发布的入口:post 方法
发布事件时,post 方法会将事件载体的类型包装成 EventType 并加入队列,再触发事件分发:

public void post(Object event, String tag) {if (event == null) {Log.e(this.getClass().getSimpleName(), "The event object is null");return;}// 将事件类型(如 LinkedHashMap.class)和 tag 包装成 EventType 入队mLocalEvents.get().offer(new EventType(event.getClass(), tag));// 分发事件mDispatcher.dispatchEvents(event);
}

2. EventType 的作用
EventType 是事件的 “标识”,包含两个核心参数:

  • paramClass:事件载体的类(如 LinkedHashMap.class)
  • tag:用于区分订阅者的标签(如 GUIDE_AUDIO_TEXT)
public final class EventType {Class<?> paramClass;public String tag = DEFAULT_TAG;public EventType(Class<?> aClass, String aTag) {paramClass = aClass;tag = aTag;}
}

3. 事件分发:dispatchEvents 方法
dispatchEvents 从队列中取出 EventType,并调用 deliveryEvent 处理:

void dispatchEvents(Object aEvent) {Queue<EventType> eventsQueue = mLocalEvents.get();if (eventsQueue != null) {EventType eventType;// 循环取出队列中的 EventType 并处理while ((eventType = eventsQueue.poll()) != null) {deliveryEvent(eventType, aEvent);}}
}

4. 匹配订阅者:deliveryEvent 与 getMatchedEventTypes 方法
deliveryEvent 的作用是找到所有匹配的 EventType,并分发给订阅者。关键逻辑在 getMatchedEventTypes:

private void deliveryEvent(EventType type, Object aEvent) {// 找到所有匹配的 EventTypeList<EventType> eventTypes = getMatchedEventTypes(type, aEvent);// 遍历匹配的 EventType,逐个触发订阅者方法for (EventType eventType : eventTypes) {handleEvent(eventType, aEvent); // 调用订阅者方法}
}

getMatchedEventTypes 会通过 findMatchEventTypes 生成匹配的 EventType 列表,并缓存结果:

private List<EventType> getMatchedEventTypes(EventType type, Object aEvent) {List<EventType> eventTypes = null;if (mCacheEventTypes.containsKey(type)) {eventTypes = mCacheEventTypes.get(type);} else {// 生成匹配的 EventType 列表eventTypes = mMatchPolicy.findMatchEventTypes(type, aEvent);mCacheEventTypes.put(type, eventTypes);}return eventTypes != null ? eventTypes : new ArrayList<EventType>();
}

5. 核心问题:findMatchEventTypes 与 addInterfaces 方法
findMatchEventTypes 会生成事件载体的 “类继承链” 和 “接口实现链” 对应的 EventType:

  • 遍历事件载体的类及其所有父类(如 LinkedHashMap → HashMap → AbstractMap → Object)
  • 遍历所有实现的接口(如 Map、Cloneable 等)
  • 为每个类和接口创建 EventType 并加入列表
public List<EventType> findMatchEventTypes(EventType type, Object aEvent) {Class<?> eventClass = aEvent.getClass();List<EventType> result = new LinkedList<EventType>();// 添加上所有父类对应的 EventTypewhile (eventClass != null) {result.add(new EventType(eventClass, type.tag));// 添加所有接口对应的 EventTypeaddInterfaces(result, eventClass, type.tag);eventClass = eventClass.getSuperclass();}return result;
}

问题出在 addInterfaces 方法的去重逻辑:

private void addInterfaces(List<EventType> eventTypes, Class<?> eventClass, String tag) {if (eventClass == null) return;Class<?>[] interfacesClasses = eventClass.getInterfaces();for (Class<?> interfaceClass : interfacesClasses) {// 错误:用 EventType 列表 contains 一个 Class 对象(永远为 false)if (!eventTypes.contains(interfaceClass)) { eventTypes.add(new EventType(interfaceClass, tag));addInterfaces(eventTypes, interfaceClass, tag); // 递归处理接口的父接口}}
}

eventTypes 是 List 类型,而 interfaceClass 是 Class 类型。contains 方法比较的是对象类型,导致判断永远为 false,无法去重。例如:

  • LinkedHashMap 实现 Map 接口,addInterfaces 会添加 EventType(Map.class, tag)
  • 其父类 HashMap 也实现 Map 接口,此时本应去重,但因类型不匹配,会再次添加 EventType(Map.class, tag)

最终,eventTypes 列表中会出现多个相同的 EventType(如多个 Map.class 对应的 EventType),导致 deliveryEvent 中的循环多次调用 handleEvent,订阅者也就收到了多次事件。

为什么使用自定义事件载体可以

因为自定义的载体只实现 Object 接口,List 中只存放自定义载体类和 Object 类,没有复杂的继承关系,也没有重复的情况。

结论

AndroidEventBus 对内置数据结构(如 Map 接口下的实现类)的重复接收问题,根源是:

  1. 内置数据结构有复杂的继承和接口实现链;
  2. addInterfaces 方法的去重逻辑错误(用 EventType 列表与 Class 对象比较),导致生成重复的
    EventType。

解决方案是使用自定义事件类作为载体,减少继承和接口关系的复杂度,从而避免重复分发。

http://www.dtcms.com/a/405771.html

相关文章:

  • Unity开发CI/CD工具Jenkins的安装(Windows10)
  • 按键精灵安卓/ios辅助工具,脚本开发新手教程ui界面介绍
  • Machine Learning HW4 report: 语者识别 (Hongyi Lee)
  • Android 系统源码级进程保活全方案:从进程创建到后台防护
  • 在hadoop中Job提交的流程
  • 基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
  • 01MemoryOS环境搭建 python3.10
  • 建设部网站职责划定html精美登录界面源码
  • 网站建设基本步骤顺序网站的整体风格
  • Leetcode 146. LRU 缓存 哈希表 + 双向链表
  • VideollaMA 3论文阅读
  • Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
  • 《红色脉络:一部PLMN在中国的演进史诗 (1G-6G)》 第11篇 | 核心网演进终局:从EPC到5GC——微服务与“云原生”
  • k8s中的NetworkPolicy
  • 【大语言模型】大模型后训练入门指南
  • 【初学】使用 node 编写 MCP Server
  • 阿里云云原生挑战官方用例SPL
  • 销售管理软件免费版什么叫seo优化
  • Apache POI 在 Linux 无图形界面环境下因字体配置问题导致Excel导出失败的解决方案
  • 咨询顾问进阶——146页PPT详解麦肯锡-企业管理整合咨询-组织设计方案【附全文阅读】
  • 力扣995. K 连续位的最小翻转次数
  • Resources$NotFoundException
  • pg下使用 TimescaleDB并创建1亿数据
  • 自动化脚本的操作逻辑与实现
  • UVa12418 Game of 999
  • 基于51单片机的音乐弹奏系统
  • 负载均衡式的在线OJ项目编写(二)
  • 美篇在哪个网站做的外链代发工具
  • Linux高级技巧之集群部署(七)
  • 外贸做那种网站wordpress获取图片的绝对地址