目录
  1. 1. 一、EventBus 概述与设计动机
  2. 2. 二、核心架构:三大组件
    1. 2.1. 2.1 EventBus(事件总线)
    2. 2.2. 2.2 SubscriberMethod(订阅方法)
    3. 2.3. 2.3 Subscription(订阅关系)
  3. 3. 三、订阅注册流程:findSubscriberMethods
    1. 3.1. 3.1 findSubscriberMethods 的查找策略
    2. 3.2. 3.2 建立订阅关系
  4. 4. 四、事件投递流程:post 方法
  5. 5. 五、线程模式(ThreadMode)详解
  6. 6. 六、粘性事件(Sticky Events)
  7. 7. 七、编译期索引:EventBusAnnotationProcessor
  8. 8. 八、与 LiveData/Flow 的对比
  9. 9. 九、面试常问题目
解读开源框架系列-EventBus事件总线框架设计

一、EventBus 概述与设计动机

EventBus 是 Android 生态中最流行的事件总线框架之一,由 greenrobot 开发维护。它的核心思想是发布-订阅模式(Publish-Subscribe Pattern)——让组件之间以事件为媒介进行松耦合通信,避免 Activity/Fragment/Service 之间的强依赖。

与 Android 原生的跨组件通信方式相比:

  • Intent / BroadcastReceiver:依赖系统级 IPC,性能开销大,且需要序列化数据。
  • Handler / Message:需要持有对方的引用,耦合度高,且不支持一对多广播。
  • 接口回调(Callback):需要定义接口并持有引用,随着业务复杂化会陷入回调地狱。
  • EventBus:零配置的发布订阅,一行注解即可,且线程调度灵活。

EventBus 的 GitHub 仓库是 https://github.com/greenrobot/EventBus。本文基于 EventBus 3.x 源码分析,核心源码包路径为 org.greenrobot.eventbus

二、核心架构:三大组件

EventBus 的架构由三个核心组件构成:

2.1 EventBus(事件总线)

单例对象(也可通过 EventBus.builder() 自定义),负责统筹全局。getDefault() 使用双重检查锁定(DCL)确保单例线程安全:

// org.greenrobot.eventbus.EventBus
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}

核心数据字段:

// 按事件类型索引的订阅者集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 按订阅者对象索引的事件类型集合
private final Map<Object, List<Class<?>>> typesBySubscriber;
// 粘性事件(sticky events)存储
private final Map<Class<?>, Object> stickyEvents;

注意 subscriptionsByEventType 使用 CopyOnWriteArrayList,这是一种在遍历时不加锁的并发容器,非常适合读多写少的订阅分发场景。

2.2 SubscriberMethod(订阅方法)

封装了一个被 @Subscribe 注解的方法的元数据:

public class SubscriberMethod {
final Method method; // java.lang.reflect.Method 对象
final ThreadMode threadMode; // 线程模式
final Class<?> eventType; // 事件类型(方法的参数类型)
final int priority; // 优先级
final boolean sticky; // 是否接收粘性事件
String methodString; // 用于比较的字符串,避免重复注册
}

2.3 Subscription(订阅关系)

封装了一次具体的订阅关系——哪个订阅者对象的哪个方法订阅了事件:

final class Subscription {
final Object subscriber; // 订阅者对象(Activity/Fragment/任意对象)
final SubscriberMethod subscriberMethod; // 订阅方法元数据
volatile boolean active; // 是否活跃(已取消注册则为 false)
}

三、订阅注册流程:findSubscriberMethods

当调用 EventBus.getDefault().register(this) 时,EventBus 会启动订阅者注册流程。

3.1 findSubscriberMethods 的查找策略

// org.greenrobot.eventbus.SubscriberMethodFinder
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods;
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
}
return subscriberMethods;
}

ignoreGeneratedIndex 是一个性能开关。EventBus 提供了一个 APT(Annotation Processing Tool)模块 eventbus-annotation-processor,可在编译期生成索引文件,避免运行时的反射查找:

// 使用编译期索引
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

当没有使用索引时,findUsingReflection 通过 Java 反射查找订阅方法:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState); // 搜索当前类
findState.moveToSuperclass(); // 向上搜索父类
}
return getMethodsAndRelease(findState);
}

private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
// 必须是 public 且非 abstract、非 static
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) { // 只有一个参数(事件类型)
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType,
threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
}
}
}
}

关键细节:

  • 方法必须是 public、非 abstract、非 static、非 bridge/synthetic
  • 方法必须有且仅有一个参数,该参数的类型就是事件类型。
  • 会遍历整个继承链(从子类到 java.lang.Object),父类的 @Subscribe 方法也会被注册。
  • FindState.checkAdd() 会检查方法签名是否重复——如果子类重写了父类的 @Subscribe 方法,子类方法会覆盖父类方法。

3.2 建立订阅关系

找到所有 SubscriberMethod 后,EventBus 将它们按事件类型分组存入 subscriptionsByEventType

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass()
+ " already registered to event " + eventType);
}
}
// 按 priority 排序插入
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 更新 typesBySubscriber 映射
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
}

四、事件投递流程:post 方法

// EventBus.java
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);

if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

关键设计——ThreadLocal 隔离:

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};

PostingThreadState 是线程独立的,每个调用 post 的线程都有自己的事件队列。这避免了多个线程并发 post 时的锁竞争。

postSingleEvent 方法会沿事件类型的继承链查找订阅者:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// ... 遍历 eventClass 及其父类/接口
// eventInheritance 标识是否查找父类事件
// 如果事件是自定义类,其父类的订阅者也会收到通知
}

查找订阅者后,调用 postToSubscription 根据线程模式分派:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: "
+ subscription.subscriberMethod.threadMode);
}
}

五、线程模式(ThreadMode)详解

EventBus 定义了五种线程模式,这是其最精妙的设计之一:

模式 说明 内在实现
POSTING 与 post() 在同一线程执行,开销最小 直接反射调用
MAIN 在主线程(UI 线程)执行 通过 Handler 切换到主线程
MAIN_ORDERED 在主线程执行,但按入队顺序串行 队列 + Handler
BACKGROUND 在后台线程执行(单线程池) Single-thread Executor
ASYNC 在任意线程池线程执行 Cached Thread Pool

BackgroundPoster 的实现:

// org.greenrobot.eventbus.BackgroundPoster
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue; // 链表实现的无界队列

@Override
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}

@Override
public void run() {
// 循环处理队列中的事件
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
}
}

BackgroundPoster 使用 PendingPostQueue(单向链表),配合对象池(PendingPost.obtainPendingPost)来复用 PendingPost 对象,减少 GC 压力。

六、粘性事件(Sticky Events)

粘性事件是一种特殊的投递机制:事件在发送时被缓存,后续注册的订阅者也能收到之前发送的事件。这在某些场景非常有用,比如先加载了数据,后续打开的 Fragment 仍能获取到数据。

public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event); // 同时触发正常投递
}

注册时检查粘性事件:

// 在 EventBus.subscribe() 中
if (subscriberMethod.sticky) {
// 检查继承链上的粘性事件
for (Map.Entry<Class<?>, Object> entry : stickyEvents.entrySet()) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}

七、编译期索引:EventBusAnnotationProcessor

EventBus 的 APT 模块 eventbus-annotation-processor 在编译期扫描所有 @Subscribe 注解,生成一个索引类:

// 生成的代码示例
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {
SUBSCRIBER_INDEX = new HashMap<>();
putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent", EventMessage.class, ThreadMode.MAIN, 0, false),
}));
}

@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
return SUBSCRIBER_INDEX.get(subscriberClass);
}
}

使用索引后,findUsingInfo() 方法直接从索引中读取 SubscriberMethod 列表,无需反射扫描所有方法,性能提升显著——尤其在包含大量非订阅方法的类中。

八、与 LiveData/Flow 的对比

特性 EventBus LiveData Kotlin Flow
生命周期感知 需自行 unregister 自动感知 通过 lifecycleScope
粘性事件 支持 天然粘性(observe 时获取最新值) 使用 StateFlow
线程切换 内置 5 种模式 默认主线程 通过 flowOn
背压 不支持 不支持(只有最新值) 原生支持
适用场景 任意组件间通信 View 层与 ViewModel 之间 数据层到 UI 层

LiveData 天然与生命周期绑定(LifecycleOwner),observe() 方法内部通过 LifecycleBoundObserver 监听 Lifecycle 的 DESTROYED 事件自动移除观察者。但 LiveData 不支持线程切换,若需要在子线程中更新数据,必须调用 postValue() 而非 setValue()

九、面试常问题目

Q1: EventBus 注册时为什么要遍历父类?

因为 @Subscribe 注解的方法可能在父类中定义(如 BaseActivity 中定义了通用的事件处理方法)。通过遍历从子类到 Object 的整个继承链,确保所有父类的订阅方法都被注册。FindState 通过 moveToSuperclass() 向上递归,直到 skipSuperClasses 被设为 true。

Q2: EventBus 的 post() 方法是线程安全的吗?如何保证?

是线程安全的。通过 ThreadLocal<PostingThreadState> 为每个线程维护独立的事件队列,避免锁竞争。CopyOnWriteArrayList 用于存储订阅者列表,写入时复制,遍历时不加锁,是高并发的经典优化手段。

Q3: EventBus 为什么不使用动态代理(如 Retrofit 的方式)?

动态代理需要接口定义,而 EventBus 的设计目标是零配置——用户只需在任意方法上加 @Subscribe 注解即可。使用反射直接调用 Method 是最直接的方式。编译期索引(Subscriber Index)通过 APT 在编译时生成元数据,规避了运行时反射的性能开销。

Q4: 粘性事件如何避免内存泄漏?

粘性事件存储在 stickyEvents Map 中,Key 是事件类型 Class 对象(不会被 GC),Value 是事件实例。最好的实践是:事件处理完后调用 EventBus.getDefault().removeStickyEvent(eventClass) 移除粘性事件。也可以使用 removeAllStickyEvents() 清空所有粘性事件。


参考源码路径:

  • EventBus 核心仓库:https://github.com/greenrobot/EventBus
  • 核心类:org.greenrobot.eventbus.EventBus
  • 订阅方法查找:org.greenrobot.eventbus.SubscriberMethodFinder
  • 线程模式枚举:org.greenrobot.eventbus.ThreadMode
  • 后台投递:org.greenrobot.eventbus.BackgroundPoster
  • 索引生成器:org.greenrobot.eventbus.annotationprocessor.EventBusAnnotationProcessor
  • Android 主线程投递:org.greenrobot.eventbus.HandlerPoster
打赏
  • 微信
  • 支付宝

评论