AndroidEventBus 发布者发布一次订阅者接收到多次问题
在使用 AndroidEventBus 时,可能会遇到一个异常现象:发布者仅发送一次事件,订阅者却接收到多次。本文将分析这一问题的原因,并提供解决方案。
先说结论和解决措施
AndroidEventBus 对内置数据结构(如 Map 接口下的实现类如 LinkedHashMap 等)的重复接收问题,根源是:
-
内置数据结构有复杂的继承和接口实现链;
-
EventBus 内部的 addInterfaces 方法的去重逻辑错误(用 EventType 列表与 Class 对象比较),导致生成重复的 EventType。eventTypes.contains(interfaceClass)) 结果永远是 false,因为 eventTypes 是 List< EventType > 类型,而 interfaceClass 是 Class 类型,无法比较
-
遍历 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 接口下的实现类)的重复接收问题,根源是:
- 内置数据结构有复杂的继承和接口实现链;
- addInterfaces 方法的去重逻辑错误(用 EventType 列表与 Class 对象比较),导致生成重复的
EventType。
解决方案是使用自定义事件类作为载体,减少继承和接口关系的复杂度,从而避免重复分发。