一、项目定位与功能
- EventBus 主要用于简化组件间通信,解耦事件发送者和接收者,适用于 Activity、Fragment 及后台线程等场景。
- 支持多线程事件分发、订阅者优先级、粘性事件(sticky events)等高级特性。
- 体积小、速度快,广泛应用于大量实际项目中。
二、实现流程解析
根据源码,EventBus 的通信实现流程如下:
1. 注册订阅者(register)
- 用户通过 EventBus.getDefault().register(this) 注册订阅者对象。
- EventBus 会用 SubscriberMethodFinder 查找该对象中所有被 @Subscribe 注解的方法(即订阅方法),这些方法必须是 public、返回值为 void,且参数只有一个(事件类型)。
- 每个订阅方法会被封装为 SubscriberMethod,再与订阅者对象一起封装为 Subscription。
- EventBus 会将 Subscription 按事件类型存入 subscriptionsByEventType,并按优先级排序。
- 同时,记录订阅者与其订阅事件类型的映射,便于后续注销。
2. 发送事件(post)
- 用户通过 EventBus.getDefault().post(event) 发送事件。
- EventBus 会根据事件的类型,查找所有订阅了该类型(及其父类/接口,若开启继承)的订阅者。
- 对每个订阅者,根据其订阅方法的 ThreadMode(线程模式)决定事件分发方式:
- POSTING:直接在当前线程调用订阅方法。
- MAIN/MAIN_ORDERED:在主线程(UI线程)调用,若当前不是主线程则通过主线程 Poster 切换。
- BACKGROUND:在后台线程调用,若当前是主线程则切换到后台线程。
- ASYNC:始终在独立线程池中异步调用。
- 事件通过反射调用订阅方法(或通过注解处理器生成的索引,避免反射)。
3. 注销订阅者(unregister)
- 用户通过 EventBus.getDefault().unregister(this) 注销订阅者。
- EventBus 会移除该对象所有的订阅关系,防止内存泄漏和重复接收事件。
4. 粘性事件(Sticky Event)
- 通过 postSticky 发送的事件会被缓存,后注册的订阅者如果声明 sticky=true,会立即收到最近的粘性事件。
5. 事件分发的线程模型
- 通过 @Subscribe(threadMode = ThreadMode.XXX) 指定事件处理线程,支持多种线程切换,适应不同场景(UI、后台、异步等)。
6. 订阅方法查找机制
- 默认通过反射查找(findUsingReflection),也支持通过注解处理器在编译期生成索引(findUsingInfo),提升性能。
- 查找时会递归父类,支持事件和订阅者的继承。
7. 错误与异常处理
- 如果订阅方法抛出异常,EventBus 会根据配置决定是否抛出、记录日志或发送 SubscriberExceptionEvent。
- 如果没有订阅者接收事件,可配置是否发送 NoSubscriberEvent。
8. 关键数据结构
- subscriptionsByEventType:事件类型 -> 订阅者列表(按优先级排序)
- typesBySubscriber:订阅者对象 -> 事件类型列表
- stickyEvents:事件类型 -> 最近的粘性事件
EventBus 通过注册/注销订阅者、事件类型与订阅方法的映射、灵活的线程切换和高效的事件分发机制,实现了松耦合、跨线程的组件通信。其核心在于订阅方法的查找与缓存、事件分发的线程模型,以及对粘性事件和异常的支持。
三、实现细节拆分
EventBus 注册流程(register)
1. 用户调用 register
- 入口:EventBus.getDefault().register(this)
- 参数:subscriber(订阅者对象)
2. 查找订阅方法
- 调用 subscriberMethodFinder.findSubscriberMethods(subscriberClass)
- 优先使用编译期生成的索引(如果有),否则用反射查找。
- 递归遍历类及其父类,查找所有被 @Subscribe 注解、public、参数唯一的方法。
- 每个方法被封装为 SubscriberMethod,包含方法、事件类型、线程模式、优先级、是否粘性等信息。
- 结果缓存到 METHOD_CACHE,提升后续注册效率。
3. 注册每个订阅方法
- 遍历所有 SubscriberMethod,对每个方法调用 subscribe(subscriber, subscriberMethod):
- 以事件类型为 key,获取或新建 subscriptionsByEventType(事件类型 -> 订阅者列表)。
- 检查是否已注册,防止重复注册。
- 按优先级插入到订阅者列表(CopyOnWriteArrayList)。
- 记录订阅者与事件类型的映射到 typesBySubscriber(订阅者 -> 事件类型列表)。
- 如果该方法声明为 sticky,则立即检查是否有对应的粘性事件,若有则直接分发一次。
4. 线程安全
- 注册过程用 synchronized 保证线程安全,防止并发注册导致数据结构混乱。
5. 注册完成
- 订阅者对象的所有订阅方法都已被 EventBus 记录,后续只要有对应事件发布,就会自动分发到这些方法。
关键数据结构
- subscriptionsByEventType:事件类型 -> 订阅者列表(按优先级排序)
- typesBySubscriber:订阅者对象 -> 事件类型列表
- METHOD_CACHE:订阅者类 -> 订阅方法列表(加速查找)
注册流程时序图(简化)
register(subscriber)
└─> findSubscriberMethods(subscriberClass)
└─> 反射/索引查找所有@Subscribe方法
└─> for each SubscriberMethod
└─> subscribe(subscriber, subscriberMethod)
└─> 订阅关系写入数据结构
└─> 检查并分发粘性事件(如有)
注册流程的核心是查找订阅方法和建立事件类型与订阅者的映射,为后续事件分发打下基础。整个过程兼顾了性能(缓存/索引)、灵活性(支持继承、粘性事件)、线程安全和易用性。
EventBus 事件发送(post)流程
1. 用户调用 post
- 入口:EventBus.getDefault().post(event)
- 参数:event(事件对象)
2. 事件入队与线程状态管理
- 获取当前线程的 PostingThreadState(线程本地变量,保证线程安全)。
- 将事件加入 eventQueue 队列。
- 如果当前没有正在分发事件(isPosting == false),则设置 isPosting = true,并记录当前是否主线程。
3. 事件分发主循环
- 进入循环,依次处理 eventQueue 中的每个事件,调用 postSingleEvent(event, postingState)。
4. 事件类型查找与继承支持
- 判断是否开启事件继承(eventInheritance)。
- 若开启,则查找事件的所有父类和接口类型,依次分发。
- 否则只分发给事件本身类型的订阅者。
5. 分发给每个订阅者
- 对每个事件类型,查找 subscriptionsByEventType 中的订阅者列表。
- 遍历每个 Subscription,设置当前分发状态(event、subscription)。
- 调用 postToSubscription(subscription, event, isMainThread) 进行实际分发。
6. 线程模型分发
- postToSubscription 根据订阅方法的 ThreadMode 决定分发方式:
- POSTING:直接在当前线程调用。
- MAIN/MAIN_ORDERED:主线程调用,必要时切换到主线程。
- BACKGROUND:后台线程调用,必要时切换到后台线程。
- ASYNC:始终在独立线程池异步调用。
- 线程切换通过 mainThreadPoster、backgroundPoster、asyncPoster 实现。
7. 反射调用订阅方法
- 最终通过 invokeSubscriber(subscription, event) 反射调用订阅方法。
- 若方法抛出异常,根据配置决定是否抛出、记录日志或发送 SubscriberExceptionEvent。
8. 事件分发终止
- 如果订阅方法调用了 cancelEventDelivery,则本次事件不会继续分发给后续订阅者。
9. 无订阅者处理
- 如果没有任何订阅者接收该事件,根据配置可发送 NoSubscriberEvent。
10. 状态恢复
- 事件分发结束后,重置 isPosting、isMainThread 等状态。
关键点总结
- 事件队列:支持嵌套/递归 post,保证事件顺序。
- 事件类型继承:支持事件分发到父类/接口的订阅者。
- 线程模型:灵活支持主线程、后台线程、异步线程分发。
- 异常处理:可配置是否抛出、记录或分发异常事件。
- 分发终止:高优先级订阅者可中断事件继续分发。
post 流程时序图(简化)
post(event)
└─> eventQueue.add(event)
└─> while (!eventQueue.isEmpty())
└─> postSingleEvent(event, postingState)
└─> for each eventType (含继承)
└─> for each Subscription
└─> postToSubscription(subscription, event, isMainThread)
└─> (根据ThreadMode切换线程)
└─> invokeSubscriber(subscription, event)
四、常见提问
1、EventBus的优点有哪些,如果不使用EventBus如何解决对应的问题?
EventBus 的优点
- 解耦
- 事件发送者和接收者完全解耦,无需直接引用对方,降低模块间依赖。
- 简化代码
- 只需注册/注销和注解订阅方法,无需手动维护回调、接口、广播等繁琐逻辑。
- 线程切换灵活
- 支持主线程、后台线程、异步线程等多种分发模式,适应 UI、IO、计算等不同场景。
- 性能高
- 采用索引+缓存机制,事件分发效率高,适合高频通信。
- 支持粘性事件
- 粘性事件可让后注册的订阅者也能收到最近一次事件,适合如“全局状态通知”等场景。
- 继承支持
- 事件类型和订阅者都支持继承,灵活性强。
- 生命周期友好
- 适合 Android Activity/Fragment 生命周期,注册/注销简单安全。
- 广泛应用与社区支持
- 经过大量项目验证,文档丰富,社区活跃。
如果不用 EventBus,常见的替代方案
- 接口回调(Callback)
- 发送方持有接收方接口引用,直接调用方法。
- 缺点:强耦合,难以跨模块/跨层传递,代码冗长。
- 观察者模式(Observer/Listener)
- 典型如 Java 的 Observer、Android 的 OnClickListener。
- 缺点:需要手动维护注册/注销,跨线程处理复杂。
- Handler/Message(Android)
- 用于线程间通信,主线程与子线程交互。
- 缺点:只能用于 Handler 所在线程,API繁琐,难以全局广播。
- BroadcastReceiver(Android)
- 用于全局广播事件。
- 缺点:系统级广播开销大,数据类型受限,注册/注销繁琐。
- LiveData(Android Jetpack)
- 支持生命周期感知的事件分发,适合 MVVM 架构。
- 缺点:主要用于数据驱动 UI,跨模块/全局事件不如 EventBus 灵活。
- RxJava/RxBus
- 用响应式流实现事件总线。
- 缺点:学习曲线陡峭,API复杂,容易内存泄漏。
总结
- 不用 EventBus 时,事件通信往往需要手动维护对象引用、接口、注册表、线程切换等,代码复杂且易出错。
- EventBus 通过统一的事件总线、注解、线程模型等机制,大大简化了事件通信,提升了开发效率和代码可维护性。
2、EventBus支持事件取消机制吗?
是的,EventBus 支持事件的取消机制。
具体机制
- 在订阅方法中,可以调用 EventBus.getDefault().cancelEventDelivery(event) 来中断当前事件的后续分发。
- 这样,优先级较高的订阅者可以“拦截”事件,阻止事件继续传递给其他订阅者。
使用限制
- 只能在事件处理方法内部调用,且必须是 ThreadMode.POSTING(即事件在发送线程直接分发时)。
- 只能取消当前正在处理的事件,不能取消其他事件。
- 如果订阅方法的 ThreadMode 不是 POSTING,调用会抛出异常。
示例代码
Apply to SubscriberMe...
@Subscribe(threadMode = ThreadMode.POSTING, priority = 10)
public void onEvent(MyEvent event) {
// 某些条件下不希望后续订阅者再收到该事件
EventBus.getDefault().cancelEventDelivery(event);
}
源码依据
- EventBus 的 cancelEventDelivery(Object event) 方法会设置当前线程的 PostingThreadState.canceled = true,后续分发检测到后会中断循环。
典型应用场景
- 事件被高优先级订阅者消费后,不希望再被其他订阅者处理(如“拦截”某些全局事件)。
总结:EventBus 支持事件取消,但仅限于 ThreadMode.POSTING 的订阅方法中使用,适合实现事件的“拦截”或“优先消费”机制。
3、EventBus如何保证事件的有序性的?
EventBus 保证事件有序性的核心机制如下:
1. 事件队列(eventQueue)顺序处理
- EventBus 内部为每个线程维护了一个 PostingThreadState,其中包含一个 eventQueue(事件队列)。
- 当你调用 post(event) 时,事件会被按顺序加入队列。
- 事件分发时,始终是先进先出(FIFO)地从队列取出并处理,保证了事件的发送顺序和处理顺序一致。
2. 单线程串行分发
- 在同一个线程内,事件的分发是串行的。
- 只有当前事件分发完成后,才会处理下一个事件,避免了并发导致的顺序错乱。
3. 订阅者优先级与顺序
- 对于同一事件类型的多个订阅者,EventBus 会根据 @Subscribe(priority=...) 的优先级从高到低依次分发。
- 同优先级的订阅者,按照注册顺序分发。
4. 线程模型影响
- ThreadMode.POSTING、ThreadMode.MAIN、ThreadMode.BACKGROUND、ThreadMode.ASYNC 等线程模式会影响事件在哪个线程被处理。
- 同一线程模式下,事件分发顺序依然严格按照事件发送顺序和订阅者优先级。
- 不同线程模式下,事件可能在不同线程并发处理,但每个线程内部依然保证顺序。
5. 嵌套/递归 post 也有序
- 如果在订阅方法中再次调用 post(),新事件会被加入当前线程的 eventQueue,依然保证顺序处理。
结论
EventBus 通过每线程独立的事件队列和串行分发机制,保证了事件的有序性。只要事件在同一线程、同一事件类型下,分发顺序就是发送顺序。
4、EventBus是如何处理粘性事件的?
1. 粘性事件的定义
- 粘性事件是指:事件发送后会被缓存,即使订阅者在事件发送之后才注册,也能立刻收到最近一次的该类型事件。
2. 发送粘性事件
- 通过 postSticky(Object event) 方法发送粘性事件。
- 实现方式:
- 事件会被存入 stickyEvents(一个 Map,key 为事件类型,value 为事件对象)。
- 然后像普通事件一样分发给当前已注册的订阅者。
java
Apply to SubscriberMe...
EventBus.getDefault().postSticky(new MyEvent());
3. 注册粘性事件订阅者
- 订阅方法需加 @Subscribe(sticky = true) 注解。
- 当订阅者注册时,EventBus 会检查 stickyEvents,如果有对应类型的粘性事件,会立即调用订阅方法,让订阅者收到最近的事件。
java
Apply to SubscriberMe...
@Subscribe(sticky = true)
public void onStickyEvent(MyEvent event) {
// 处理粘性事件
}
4. 获取/移除粘性事件
- 获取:getStickyEvent(Class eventType) 可获取某类型的粘性事件。
- 移除:removeStickyEvent(Class eventType) 或 removeAllStickyEvents() 可移除单个或全部粘性事件。
5. 粘性事件的典型应用场景
- 全局状态通知(如登录状态、配置变更等)。
- 需要“补发”给后注册订阅者的事件。
6. 源码关键点
- 粘性事件存储在 stickyEvents(ConcurrentHashMap<Class<?>, Object>)。
- 注册时如果订阅方法声明 sticky = true,会自动查找并分发粘性事件。
总结
EventBus 通过 postSticky 发送并缓存事件,注册时自动补发给 sticky 订阅者,从而实现了粘性事件机制。