一、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)确保单例线程安全:
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;private final Map<Class<?>, Object> stickyEvents;
注意 subscriptionsByEventType 使用 CopyOnWriteArrayList,这是一种在遍历时不加锁的并发容器,非常适合读多写少的订阅分发场景。
2.2 SubscriberMethod(订阅方法) 封装了一个被 @Subscribe 注解的方法的元数据:
public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class<?> eventType; final int priority; final boolean sticky; String methodString; }
2.3 Subscription(订阅关系) 封装了一次具体的订阅关系——哪个订阅者对象的哪个方法订阅了事件:
final class Subscription { final Object subscriber; final SubscriberMethod subscriberMethod; volatile boolean active; }
三、订阅注册流程:findSubscriberMethods 当调用 EventBus.getDefault().register(this) 时,EventBus 会启动订阅者注册流程。
3.1 findSubscriberMethods 的查找策略 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(); 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); } } 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 ; } } List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null ) { subscribedEvents = new ArrayList <>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); }
四、事件投递流程:post 方法 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 ; }
查找订阅者后,调用 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 的实现:
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); }
注册时检查粘性事件:
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