EventBus事件总设计
# 04.EventBus事件总线设计原理
# 目录介绍
- 01.事件总线设计思想
- 1.1 事件总线解决痛点
- 1.2 事件总线核心思想
- 02.EventBus设计思想
- 2.1 三要素的设计
- 2.2 线程模型的设计
- 2.3 如何简单使用设计
- 2.4 核心设计思想
- 2.5 如何发布事件设计
- 2.6 如何订阅事件设计
- 2.7 如何处理不同线程
- 2.8 如何设计优先级
- 2.9 如何设计取消事件
- 2.10 设计事件传递方式
- 03.EventBus注册设计
- 3.1 获取Bus对象
- 3.2 注册事件源码
- 3.3 订阅者的注册过程
- 04.EventBus事件分发解析
- 4.1 从post方法入手
- 4.2 PostingThreadState设计
- 4.3 postSingleEvent方法
- 4.4 postToSubscription方法
- 4.5 整个流程图和总结
- 05.EventBus架构设计分析
- 5.1 整体架构图
- 5.2 核心数据结构
- 5.3 APT编译时索引原理
- 06.优秀代码设计深度解析
- 6.1 设计模式应用全景
- 6.2 HandlerPoster源码深度分析
- 6.3 BackgroundPoster串行设计解析
- 6.4 PostingThreadState的ThreadLocal设计
- 6.5 CopyOnWriteArrayList的选型思考
- 6.6 METHOD_CACHE缓存策略设计
- 6.7 FindState对象池复用设计
- 6.8 EventBusBuilder建造者模式解析
- 6.9 Subscription的active标记设计
- 6.10 设计思想总结
- 07.EventBus使用注意事项
- 7.1 常见的坑
- 7.2 EventBus与其他方案对比
- 08.面试高频问题深度解析
- 8.1 经典面试题
- 8.2 进阶面试题
# 01.事件总线设计思想
# 1.1 事件总线解决痛点
主要解决三大痛点问题
- 解决传统组件通信耦合性问题:在传统组件之间的通信通常是通过直接引用或回调实现的,这样会导致组件之间的耦合性增加。使用事件总线可以帮助解耦组件,减少组件之间的直接依赖,提高代码的灵活性和可维护性。
- 解决跨组件通信痛点:在复杂的应用中,不同组件之间可能需要进行跨界通信,传统的方式可能会导致通信逻辑复杂混乱。事件总线提供了一种简单的方式来实现跨组件通信,使得不同组件之间的通信更加简单和直观。
- 解决简化多对多通信痛点:有些场景下,多个组件需要同时订阅同一个事件,传统的通信方式可能会导致复杂的逻辑处理。事件总线采用发布/订阅模式,简化了多个组件之间的通信逻辑。
# 1.2 事件总线核心思想
在 Android 开发中,事件总线是一种常用的设计模式,用于简化组件之间的通信和解耦。事件总线的设计思想:
- 解耦组件:事件总线允许组件之间进行解耦,即发送者和接收者之间不直接进行耦合,而是通过事件总线来进行通信。这样可以降低组件之间的依赖性,使得组件更加独立和可复用。
- 发布/订阅模式:事件总线通常采用发布/订阅模式,其中发送者(发布者)发送事件到事件总线,而接收者(订阅者)从事件总线订阅感兴趣的事件。这种模式使得多个组件可以同时订阅同一个事件,实现了一对多的通信。
- 异步通信:事件总线通常支持异步通信,即发送事件和处理事件的过程是异步的。这样可以避免在主线程中进行耗时操作,提高应用的响应性和性能。
- 线程安全:好的事件总线库通常会考虑线程安全性,确保在多线程环境下能够正确地处理事件的发送和接收,避免出现竞态条件和线程安全问题。
- 多事件类型:事件总线通常支持不同类型的事件,可以是系统定义的事件(如网络状态变化、数据更新等),也可以是自定义的事件(如用户操作、UI更新等)。这样可以满足不同场景下的通信需求。
- 灵活性和扩展性:事件总线应该具有一定的灵活性和扩展性,允许开发者自定义事件类型、事件处理逻辑和事件传递方式,以满足不同的业务需求。
# 02.EventBus设计思想
# 2.1 三要素的设计
EventBus的三要素
1.Event:事件,可以是任意类型的对象。
2.Subscriber:事件订阅者
在EventBus3.0之前,消息处理的方法只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他们分别代表四种线程模型。
在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),四种线程模型下面会讲到。
3.Publisher:事件发布者
可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()就好了,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
# 2.2 线程模型的设计
EventBus的四种ThreadMode(线程模型)
- POSTING(默认):如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
- MAIN:事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。
- BACKGROUND: 如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
- ASYNC:无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
# 2.3 如何简单使用设计
代码如下: EventBus.getDefault().post(param); 调用原理简单理解为:
一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。
撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。
它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?
自定义一个事件类
public class MessageEvent { }1
2发送事件
EventBus.getDefault().post(messageEvent);1处理事件
- 3.0之后, 消息处理的方法可以随便取名。问题:(threadMode=ThreadMode.MAIN)是做什么用的??
需要添加一个注解@Subscribe,并且要指定线程模型。如果没有添加,那就是默认为POSTING
@Subscribe(threadMode = ThreadMode.MAIN) public void Hhhh(MessageEvent messageEvent) { ... }1
2
3
4
- 3.0之后, 消息处理的方法可以随便取名。问题:(threadMode=ThreadMode.MAIN)是做什么用的??
需要添加一个注解@Subscribe,并且要指定线程模型。如果没有添加,那就是默认为POSTING
取消事件订阅
EventBus.getDefault().unregister(this);1使用弊端分析
- EventBus好处比较明显,它能够解耦和,将业务和视图分离,代码实现比较容易。而且3.0后,我们可以通过apt预编译找到订阅者,避免了运行期间的反射处理解析,大大提高了效率。当然EventBus也会带来一些隐患和弊端,如果滥用的话会导致逻辑的分散并造成维护起来的困难。另外大量采用EventBus代码的可读性也会变差。
# 2.4 核心设计思想
EventBus的核心设计思想是发布/订阅模式 + 反射调用:
核心设计思想:
1. 数据结构设计
subscriptionsByEventType: Map<EventType, List<Subscription>>
├── Key: 事件类型(Event的Class对象)
└── Value: 该事件类型所有订阅者的列表
typesBySubscriber: Map<Subscriber, List<EventType>>
├── Key: 订阅者对象
└── Value: 该订阅者订阅的所有事件类型
这两个Map是EventBus的核心数据结构,互为索引:
→ 发送事件时:通过eventType找到所有订阅者
→ 取消注册时:通过subscriber找到所有事件类型
2. 工作流程
register(subscriber)
→ 扫描subscriber中所有@Subscribe方法
→ 将<eventType, subscription>存入subscriptionsByEventType
→ 将<subscriber, eventTypes>存入typesBySubscriber
post(event)
→ 通过event.getClass()找到所有Subscription
→ 根据ThreadMode切换到对应线程
→ 通过反射调用订阅方法
unregister(subscriber)
→ 通过subscriber找到所有eventType
→ 从subscriptionsByEventType中移除该subscriber
→ 从typesBySubscriber中移除该subscriber
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 2.5 如何发布事件设计
事件发布设计:
发布方式1:普通事件 post(event)
├── 将event加入当前线程的事件队列
├── 按顺序分发队列中的事件
├── 根据事件类型查找所有订阅者
└── 按优先级依次调用订阅方法
发布方式2:粘性事件 postSticky(event)
├── 将event保存到stickyEvents Map中(Key=eventType)
├── 然后执行普通post逻辑
├── 新的订阅者注册时,如果订阅了粘性事件
└── 会立即收到之前发送的粘性事件
粘性事件原理:
stickyEvents: Map<Class<?>, Object>
→ postSticky(event) 先存入Map,再post
→ register时如果subscriberMethod.sticky == true
→ 从stickyEvents中取出该eventType的事件
→ 立即分发给新订阅者
疑惑:粘性事件适用什么场景?
答疑:当事件发送时接收者还未注册的场景,如:
→ Activity A发送事件给尚未创建的Activity B
→ Service发送状态给尚未创建的UI组件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 2.6 如何订阅事件设计
订阅事件的设计:
注解方式(EventBus 3.0+):
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1, sticky = false)
public void onMessageEvent(MessageEvent event) {
// 处理事件
}
注解中的三个属性:
├── threadMode:指定在哪个线程执行(POSTING/MAIN/BACKGROUND/ASYNC)
├── priority:优先级,数字越大越先接收事件
└── sticky:是否接收粘性事件
订阅方法扫描:
EventBus 3.0提供两种方式查找订阅方法:
方式1:运行时反射扫描
→ 遍历subscriber类及其父类的所有方法
→ 查找标记了@Subscribe注解的public方法
→ 解析注解参数(threadMode, priority, sticky)
→ 缓存到METHOD_CACHE中避免重复扫描
方式2:编译时APT索引(推荐)
→ 使用EventBusAnnotationProcessor在编译期生成索引类
→ 运行时直接读取索引,无需反射扫描
→ 性能大幅提升,适合大型项目
// build.gradle配置
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.x'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2.7 如何处理不同线程
线程切换的实现原理:
POSTING模式:
→ 在发布事件的线程中直接调用
→ invokeSubscriber(subscription, event)
→ 通过反射调用方法:subscription.subscriberMethod.method.invoke(...)
→ 无线程切换开销
MAIN模式:
→ 判断当前是否是主线程
├── 是主线程 → 直接调用invokeSubscriber
└── 非主线程 → mainThreadPoster.enqueue(subscription, event)
→ mainThreadPoster是HandlerPoster
→ 内部使用主线程的Handler
→ 通过Handler.sendMessage()切换到主线程
→ handleMessage()中执行invokeSubscriber
BACKGROUND模式:
→ 判断当前是否是主线程
├── 非主线程 → 直接调用invokeSubscriber(当前线程执行)
└── 是主线程 → backgroundPoster.enqueue(subscription, event)
→ 使用线程池中的线程执行
→ 注意:所有BACKGROUND事件共用一个线程(串行执行)
ASYNC模式:
→ 无论在哪个线程,都提交到线程池
→ asyncPoster.enqueue(subscription, event)
→ 使用Executors.newCachedThreadPool()
→ 每个事件都在新线程中执行(并行执行)
核心组件:
├── HandlerPoster:基于Handler的主线程投递器
├── BackgroundPoster:后台线程投递器(串行)
├── AsyncPoster:异步线程投递器(并行)
└── 底层使用Executors.newCachedThreadPool()线程池
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 2.8 如何设计优先级
优先级设计:
@Subscribe(priority = 100) // 数字越大,优先级越高
实现原理:
在subscribe()注册时,按优先级插入到subscriptions列表的正确位置:
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority >
subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
效果:
subscriptions列表按优先级从高到低排列
→ post时按列表顺序遍历
→ 高优先级订阅者先收到事件
高优先级订阅者可以拦截事件:
@Subscribe(priority = 100)
public void onEvent(MessageEvent event) {
// 拦截事件,低优先级订阅者不会收到
EventBus.getDefault().cancelEventDelivery(event);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 2.9 如何设计取消事件
取消事件传递:
EventBus.getDefault().cancelEventDelivery(event);
原理:
PostingThreadState中有一个canceled字段
→ cancelEventDelivery()将canceled设为true
→ postToSubscription()循环中检查canceled
→ 如果canceled为true,停止向后续订阅者分发
限制:
→ 只能在POSTING线程模式的订阅方法中调用
→ 因为其他线程模式可能已经异步提交了
→ 在异步线程中取消没有意义
取消注册(unregister):
→ 移除subscriber对应的所有subscription
→ 已在队列中的事件仍会被分发(但subscription.active=false)
→ 分发时检查active状态,跳过已取消的订阅
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.10 设计事件传递方式
事件传递方式设计:
1. 普通事件传递
post(event) → 查找匹配的订阅者 → 依次分发
└── 同步分发,按优先级顺序
2. 粘性事件传递
postSticky(event) → 保存到stickyEvents → post
└── 新订阅者注册时自动接收最新的粘性事件
└── 每种事件类型只保留最后一个粘性事件
3. 事件继承传递(eventInheritance)
默认为true,发送子类事件时,父类事件的订阅者也能收到
// 例如:
class BaseEvent {}
class ChildEvent extends BaseEvent {}
@Subscribe
void onBase(BaseEvent e) {} // 发送ChildEvent时也会触发
@Subscribe
void onChild(ChildEvent e) {} // 只有发送ChildEvent时触发
原理:lookupAllEventTypes()查找事件的所有父类和接口
→ 对每种类型都查找订阅者并分发
→ 设置eventInheritance=false可关闭此行为(提升性能)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 03.EventBus注册设计
# 3.1 获取Bus对象
EventBus.getDefault()获取对象,先看源码,单例模式,使用了双重判断的方式,防止并发的问题,还能极大的提高效率。
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
2
3
4
5
6
7
8
9
10
11
# 3.2 注册事件源码
在需要订阅事件的地方注册事件
EventBus.getDefault().register(this);
首先看register(this)源码,这里直接获取subscriber字节码对象。
public void register(Object subscriber) {
//首先获取订阅者的类对象
Class<?> subscriberClass = subscriber.getClass();
//用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
接下来看findSubscriberMethods(subscriberClass)里面的源码。该方法的作用其实就是从订阅类中获取所有的订阅方法信息
findSubscriberMethods找出一个SubscriberMethod的集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod(订阅方法)类中,主要就是用保存订阅方法的Method对象、线程模式、事件类型、优先级、是否是粘性事件等属性。
源码分析:首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据 ignoreGeneratedIndex 选择如何查找订阅方法,ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex。最后,找到订阅方法后,放入缓存,以免下次继续查找。ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值。我们在项目中经常通过EventBus单例模式来获取默认的EventBus对象,也就是ignoreGeneratedIndex为false的情况,这种情况调用了findUsingInfo方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//首先从缓存中读取
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略注解器生成的MyEventBusIndex类
if (ignoreGeneratedIndex) {
//利用反射来获取订阅类中的订阅方法信息
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
subscriberMethods = findUsingInfo(subscriberClass);
}
//在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//保存进缓存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
//METHOD_CACHE,是一个map集合,键是class类型
Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
接下来看findUsingInfo(subscriberClass)源码,通过getSubscriberInfo方法来获取订阅者信息。在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以得到订阅方法相关的信息,这个时候就不在需要通过注解进行获取订阅方法。如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//获取订阅者信息,没有配置MyEventBusIndex返回null
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//通过反射来查找订阅方法
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.3 订阅者的注册过程
# 1.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
- 回到register(this)这个方法
- 订阅者的注册过程
- 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //获取订阅方法的参数类型 Class<?> eventType = subscriberMethod.eventType; //根据订阅者和订阅方法构造一个订阅事件 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //获取当前订阅事件中Subscription的List集合 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //订阅者已经注册则抛出EventBusException if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //遍历订阅事件,找到比subscriptions中订阅事件小的位置,然后插进去 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中 subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { //粘性事件的处理 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
- 流程图
# 02.EventBus事件分发解析
# 2.1 从post方法入手
- 首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。
/** Posts the given event to the event bus. */ public void post(Object event) { //获取当前线程的postingState PostingThreadState postingState = currentPostingThreadState.get(); //取得当前线程的事件队列 List<Object> eventQueue = postingState.eventQueue; //将该事件添加到当前的事件队列中等待分发 eventQueue.add(event); if (!postingState.isPosting) { //判断是否是在主线程post postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); 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; } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 2.2 什么是PostingThreadState?
- PostingThreadState中包含了当前线程的事件队列,就是当前线程所有分发的事件都保存在eventQueue事件队列中以及订阅者订阅事件等信息,有了这些信息我们就可以从事件队列中取出事件分发给对应的订阅者
final static class PostingThreadState { final List<Object> eventQueue = new ArrayList<Object>();//当前线程的事件队列 boolean isPosting;//是否有事件正在分发 boolean isMainThread;//post的线程是否是主线程 Subscription subscription;//订阅者 Object event;//订阅事件 boolean canceled;//是否取消 }1
2
3
4
5
6
7
8
# 2.3 PostingThreadState怎么获得?
- ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。
- 可以看出currentPostingThreadState的实现是一个包含了PostingThreadState的ThreadLocal对象,这样可以保证取到的都是自己线程对应的数据。
- 我们有了PostingThreadState获取到了当前线程的事件队列,接下来就是事件分发,我们来看postSingleEvent(eventQueue.remove(0), postingState);
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } };1
2
3
4
5
6
# 2.4 来看看postSingleEvent方法里做了什么
- eventInheritance表示是否向上查找事件的父类,它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法
事件分发 private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { //得到事件类型 Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; //是否触发订阅了该事件(eventClass)的父类,以及接口的类的响应方法. if (eventInheritance) { List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2.5 接下来看看postSingleEventForEventType方法
- 同步取出该事件对应的Subscription集合并遍历该集合将事件event和对应Subscription传递给postingState并调用postToSubscription方法对事件进行处理,接下来看看postToSubscription方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //根据事件类型获取所有的订阅者 subscriptions = subscriptionsByEventType.get(eventClass); } //向每个订阅者分发事件 if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //对事件进行处理 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2.6 接下来看看postToSubscription方法
- 源码如下所示
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING://默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法, //不论该线程是否为主线程(UI 线程)。 invokeSubscriber(subscription, event); break; case MAIN://在主线程中执行响应方法。 if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(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); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2.7 整个流程图
# 2.8 总结一下整个事件分发的过程
- 大概如下所示:
- 首先获取当前线程的PostingThreadState对象从而获取到当前线程的事件队列
- 通过事件类型获取到所有订阅者集合
- 通过反射执行订阅者中的订阅方法
# 03.EventBus取消注册解析
# 3.1 unregister(this)方法入手
- 代码如下所示
- 获取订阅者的所有订阅的事件类型,然后遍历事件类型的订阅者集合中移除订阅者
/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { //获取订阅者的所有订阅的事件类型 List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { //从事件类型的订阅者集合中移除订阅者 unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 获取订阅者的所有订阅的事件类型,然后遍历事件类型的订阅者集合中移除订阅者
# 3.2 再来看看unsubscribeByEventType(subscriber, eventType)
- 获取事件类型的所有订阅者,然后遍历订阅者集合,将解除的订阅者移除
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { //获取事件类型的所有订阅者 List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //遍历订阅者集合,将解除的订阅者移除 if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.3 取消注册流程图
# 3.4 取消注册的过程
- 总结一下取消注册的过程
- 1、首先获取订阅者的所有订阅事件
- 2、遍历订阅事件
- 3、根据订阅事件获取所有的订阅了该事件的订阅者集合
- 4、将该订阅者移除
- 5、将步骤1中的集合中的订阅者移除
# 04.总结一下EventBus的工作原理
# 4.1 订阅逻辑
- 逻辑如下所示:
- 1、首先用register()方法注册一个订阅者
- 2、获取该订阅者的所有订阅的方法
- 3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中
- 4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中
- 5、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
# 4.2 事件发送逻辑
- 逻辑如下所示:
- 1、首先获取当前线程的事件队列
- 2、将要发送的事件添加到事件队列中
- 3、根据发送事件类型获取所有的订阅者
- 4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法
# 4.3 取消逻辑
- 逻辑如下所示:
- 1、首先通过unregister方法拿到要取消的订阅者
- 2、得到该订阅者的所有订阅事件类型
- 3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者
- 4、将订阅者从步骤2的集合中移除
# 05.EventBus架构设计分析
# 5.1 整体架构图
EventBus整体架构:
┌─────────────────────────────────────────┐
│ 应用层 │
│ register() / post() / unregister() │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ EventBus核心层 │
│ ┌──────────────────────────────────┐ │
│ │ subscriptionsByEventType (Map) │ │
│ │ typesBySubscriber (Map) │ │
│ │ stickyEvents (Map) │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ SubscriberMethodFinder │ │
│ │ (反射扫描 / APT索引) │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Poster层(线程调度) │ │
│ │ HandlerPoster (MAIN) │ │
│ │ BackgroundPoster (BACKGROUND) │ │
│ │ AsyncPoster (ASYNC) │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 5.2 核心数据结构
核心数据结构详解:
Subscription = (subscriber + subscriberMethod)
├── subscriber: Object → 订阅者实例
├── subscriberMethod: SubscriberMethod → 订阅方法信息
└── active: boolean → 是否活跃
SubscriberMethod:
├── method: Method → 反射方法对象
├── threadMode: ThreadMode → 线程模式
├── eventType: Class<?> → 事件类型
├── priority: int → 优先级
└── sticky: boolean → 是否粘性
subscriptionsByEventType: Map<Class<?>, CopyOnWriteArrayList<Subscription>>
└── 线程安全的CopyOnWriteArrayList
└── 事件分发时只读不写,适合COW
typesBySubscriber: Map<Object, List<Class<?>>>
└── 反向索引,用于快速取消注册
PostingThreadState: ThreadLocal变量
└── 每个线程独立的事件队列和发送状态
└── 避免多线程竞争
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.3 APT编译时索引原理
APT(Annotation Processing Tool)索引原理:
编译时:
EventBusAnnotationProcessor扫描所有@Subscribe注解
↓
为每个模块生成索引类:
MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<>();
putIndex(new SimpleSubscriberInfo(
MainActivity.class,
true, // shouldCheckSuperclass
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onMessageEvent",
MessageEvent.class,
ThreadMode.MAIN)
}
));
}
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
return SUBSCRIBER_INDEX.get(subscriberClass);
}
}
运行时:
EventBus.builder()
.addIndex(new MyEventBusIndex()) // 传入APT生成的索引
.build()
查找订阅方法时:
→ 先从APT索引中查找(O(1)查找,无反射开销)
→ 如果索引中没有,才使用反射扫描
性能对比:
反射扫描:每个类需要遍历所有方法 → 毫秒级
APT索引:直接HashMap查找 → 微秒级
→ APT方式快约100倍
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 06.优秀代码设计深度解析
# 6.1 设计模式应用全景
EventBus虽然代码量不大(核心类不到10个),但巧妙运用了多种设计模式,值得深入学习:
EventBus中的设计模式全景:
1. 观察者模式(核心模式)
整个EventBus就是观察者模式的实现:
→ Publisher(发布者)通过post()发送事件
→ Subscriber(订阅者)通过@Subscribe接收事件
→ EventBus充当事件调度中心(中介者角色)
与标准观察者模式的区别:
传统观察者:Subject直接持有Observer列表,一对多
EventBus:通过事件类型解耦,发布者和订阅者互不知道对方
→ 更加松耦合,实际上是"发布/订阅模式"(观察者模式的变体)
2. 单例模式(双重检查锁)
EventBus.getDefault() 使用DCL(Double-Check Lock):
public static EventBus getDefault() {
EventBus instance = defaultInstance; // 局部变量减少volatile读
if (instance == null) {
synchronized (EventBus.class) {
instance = defaultInstance;
if (instance == null) {
instance = EventBus.builder().installDefaultEventBus();
}
}
}
return instance;
}
关键细节:defaultInstance声明为volatile
→ 防止指令重排导致获取到未完全初始化的对象
→ 这是DCL的必要条件
3. 建造者模式(EventBusBuilder)
EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.throwSubscriberException(true)
.eventInheritance(false)
.executorService(customExecutor)
.installDefaultEventBus();
→ 将复杂的配置过程从构造函数中分离
→ 链式调用提升可读性
→ installDefaultEventBus()保证只安装一次
4. 策略模式(ThreadMode线程切换)
不同的ThreadMode对应不同的投递策略:
→ POSTING → 直接调用(无额外策略)
→ MAIN → HandlerPoster策略
→ BACKGROUND → BackgroundPoster策略
→ ASYNC → AsyncPoster策略
postToSubscription()中的switch-case就是策略选择器
不同Poster封装了不同的线程调度策略
5. 享元模式(FindState对象池)
FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
→ 复用FindState对象,避免频繁创建
→ 用完后归还到池中
→ 减少GC压力
6. 模板方法模式(SubscriberInfoIndex)
定义查找订阅方法的骨架:
→ 先查APT索引 → 没有则反射扫描
→ findUsingInfo()定义了整体流程
→ getSubscriberInfo()是可替换的步骤
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 6.2 HandlerPoster源码深度分析
HandlerPoster是EventBus线程切换的核心组件,负责将事件投递到主线程,其设计非常精巧:
// HandlerPoster继承Handler,绑定主线程Looper
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue; // 待处理事件队列
private final int maxMillisInsideHandleMessage; // 单次handleMessage最大执行时间
private final EventBus eventBus;
private boolean handlerActive; // Handler是否正在处理消息
protected HandlerPoster(EventBus eventBus, Looper looper,
int maxMillisInsideHandleMessage) {
super(looper); // 绑定主线程Looper
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
// 从对象池获取PendingPost(享元模式)
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
// 发送消息触发handleMessage
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
pendingPost = queue.poll(); // 双重检查
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
// 执行订阅方法
eventBus.invokeSubscriber(pendingPost);
// 关键设计:时间片轮转
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
// 超过时间片,重新调度,让出主线程
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
HandlerPoster设计精髓分析:
1. 时间片轮转机制(防ANR设计)
maxMillisInsideHandleMessage默认10ms
→ 一次handleMessage中最多执行10ms
→ 超时后重新sendMessage,将自己排到消息队列末尾
→ 让其他UI消息(触摸、绘制等)有机会执行
→ 类似OS的时间片调度,防止事件处理阻塞UI
疑惑:为什么不每个事件都sendMessage?
答疑:每次sendMessage都有跨线程通信开销(约0.1ms)
如果有100个事件,100次sendMessage开销不可忽视
时间片方式:在不阻塞UI的前提下批量处理
→ 吞吐量和响应性之间的最佳平衡
2. handlerActive标志的并发设计
→ enqueue()在任意线程调用
→ handleMessage()在主线程执行
→ handlerActive避免重复sendMessage
→ synchronized保证可见性
如果没有handlerActive:
→ 连续10次enqueue会发送10条Message
→ 但这10个事件可能在第1次handleMessage中就全部处理完
→ 多余的9次handleMessage空转 → 性能浪费
3. PendingPost对象池
PendingPost.obtainPendingPost():
→ 从静态链表池中获取PendingPost对象
→ 用完后releasePendingPost()归还
→ 避免频繁创建对象 → 减少GC
→ 最大池大小10000,防止内存泄漏
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}
4. PendingPostQueue的设计
自定义的单链表队列(非使用Java标准库)
→ 只有head和tail两个指针
→ enqueue:O(1)尾插
→ poll:O(1)头取
→ 极简实现,减少锁竞争
→ synchronized只保护链表操作,不保护事件执行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 6.3 BackgroundPoster串行设计解析
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
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() {
try {
try {
while (true) {
// 等待1秒获取下一个事件
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING,
Thread.currentThread().getName() + " was interrupted", e);
}
} finally {
executorRunning = false;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BackgroundPoster vs AsyncPoster设计对比:
BackgroundPoster(BACKGROUND模式):
→ 所有BACKGROUND事件共用一个线程串行执行
→ executorRunning标志保证同时只有一个Runnable在执行
→ 适合轻量级后台任务
→ 优势:线程资源消耗少,事件按顺序执行
→ 劣势:一个事件耗时会阻塞后续所有BACKGROUND事件
AsyncPoster(ASYNC模式):
→ 每个事件都提交到线程池并行执行
→ 没有executorRunning控制,每次enqueue都execute
→ 适合耗时操作
→ 优势:事件之间互不影响
→ 劣势:大量事件时可能创建过多线程
设计决策的思考:
疑惑:为什么不只提供ASYNC,而要有BACKGROUND?
答疑:
1. 很多后台任务是轻量级的(如更新缓存、写日志)
→ 每个都开新线程是浪费
2. 某些场景需要保证事件处理顺序
→ BACKGROUND的串行特性保证了顺序
3. BackgroundPoster有1秒的等待窗口
→ 连续的BACKGROUND事件复用同一线程
→ 减少线程切换开销
这体现了"按需分配"的设计思想:
BACKGROUND → 轻量后台 → 省资源
ASYNC → 重量异步 → 保性能
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 6.4 PostingThreadState的ThreadLocal设计
PostingThreadState + ThreadLocal 设计分析:
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
private final ThreadLocal<PostingThreadState> currentPostingThreadState =
new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
设计意图分析:
1. 为什么用ThreadLocal而不是synchronized?
→ post()可能在任意线程被调用
→ 如果用synchronized:
线程A post时,线程B的post必须等待
→ 事件分发变成全局串行
→ 严重影响性能
→ 使用ThreadLocal:
每个线程有自己的PostingThreadState
→ 事件分发在各线程独立进行
→ 零竞争,零等待
2. eventQueue的作用(防递归炸栈)
场景:订阅方法中又post了新事件
onEventA(EventA a) {
EventBus.getDefault().post(new EventB());
}
如果没有eventQueue直接递归调用:
post(A) → onEventA → post(B) → onEventB → post(C) → ...
→ 深度递归 → 可能StackOverflow
使用eventQueue后:
post(A) → eventQueue=[A] → isPosting=true
→ 分发A → onEventA中post(B) → eventQueue=[B]
→ 因为isPosting=true,不会嵌套分发
→ 分发A完成 → 循环取出B → 分发B
→ 递归变成了循环 → 安全
3. isPosting + isMainThread 状态复用
→ isMainThread只在post入口判断一次
→ 后续所有事件分发共用这个判断结果
→ 避免在循环中多次调用Looper.getMainLooper()
→ 微小但有意义的性能优化
4. canceled字段的精准控制
→ 只在当前线程的当前分发轮次有效
→ finally块中重置canceled=false
→ 不影响其他线程,也不影响下次post
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 6.5 CopyOnWriteArrayList的选型思考
subscriptionsByEventType的值类型选择了CopyOnWriteArrayList,而不是ArrayList或其他集合:
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
疑惑:为什么选CopyOnWriteArrayList?
分析读写场景:
写操作(低频):register/unregister时修改subscriptions列表
读操作(高频):每次post事件时遍历subscriptions列表
CopyOnWriteArrayList的特性:
→ 写时复制:修改时创建数组副本
→ 读操作无锁:遍历时不需要同步
→ 写操作加锁:修改时ReentrantLock
→ 迭代器快照:遍历期间数组不会改变
为什么适合EventBus:
1. 事件分发(读)远多于注册/取消(写)→ 典型的读多写少
2. post遍历subscriptions时无需加锁 → 高并发下性能好
3. 遍历期间如果有unregister → 不会ConcurrentModificationException
4. 遍历的是快照 → unregister后的事件仍会被分发到已取消订阅者
→ 这就是为什么需要subscription.active检查
如果使用ArrayList + synchronized:
→ 每次post都要加锁遍历
→ 高频post场景下锁竞争严重
→ 性能远不如CopyOnWriteArrayList
如果使用ConcurrentHashMap:
→ subscriptions是有序列表(按优先级排序)
→ ConcurrentHashMap是无序的
→ 无法满足优先级需求
结论:CopyOnWriteArrayList是读多写少+需要有序+需要线程安全的最佳选择
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 6.6 METHOD_CACHE缓存策略设计
METHOD_CACHE设计分析:
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE =
new ConcurrentHashMap<>();
缓存策略:
Key: 订阅者的Class对象
Value: 该Class中所有订阅方法的列表
查找流程:
findSubscriberMethods(subscriberClass)
→ 1. 先查METHOD_CACHE
→ 2. 命中 → 直接返回(O(1))
→ 3. 未命中 → 反射扫描/APT查找
→ 4. 将结果存入METHOD_CACHE
→ 5. 返回结果
缓存设计的巧妙之处:
1. 为什么用ConcurrentHashMap而不是HashMap+synchronized?
→ 多个Activity可能同时在不同线程register
→ ConcurrentHashMap支持并发读写
→ 性能优于HashMap+synchronized
→ 不用担心多线程put的安全问题
2. 缓存粒度的选择
→ 以Class为Key而非以Method为Key
→ 一个Class的所有订阅方法一次性缓存
→ 同一个Activity类的多个实例共享缓存
→ 只有首次注册某个类型时需要扫描
3. 缓存的生命周期
→ METHOD_CACHE是static的 → 应用生命周期内不释放
→ Class对象不会被回收(ClassLoader持有)
→ 不存在缓存失效问题
→ 空间换时间的经典策略
4. 缓存对反射性能的影响
无缓存时(假设100个Activity,每个5个订阅方法):
每次register都要反射扫描 → 100 × 5 = 500次反射
有缓存时:
每种Activity类型只扫描一次 → 最多100次反射(首次)
后续实例直接使用缓存 → 0次反射
→ 缓存效果在Activity多次创建销毁的场景中非常显著
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 6.7 FindState对象池复用设计
// FindState对象池 — 经典的数组对象池实现
private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private FindState prepareFindState() {
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] != null) {
FindState state = FIND_STATE_POOL[i];
FIND_STATE_POOL[i] = null; // 取出后置空
return state;
}
}
}
return new FindState(); // 池中没有则新建
}
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle(); // 重置状态
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState; // 归还到池中
break;
}
}
}
return subscriberMethods;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
对象池设计分析:
1. 为什么池大小是4?
→ 实际并发调用findSubscriberMethods的线程数不会太多
→ 通常在主线程或少数几个线程中register
→ 4个已经足够覆盖绝大多数并发场景
→ 池太大浪费内存,太小则起不到复用效果
2. 为什么用数组而不是List/Queue?
→ 数组是最轻量的集合,无额外对象开销
→ 固定大小,不需要扩容
→ 遍历速度快
→ synchronized范围小(只保护数组访问)
3. FindState中包含哪些可复用状态?
→ subscriberMethods列表
→ anyMethodByEventType Map
→ subscriberClassByMethodKey Map
→ subscriberInfo引用
→ clazz引用
→ recycle()重置所有状态,使其可安全复用
4. 对比Android中的类似设计
→ Message.obtain() — 消息对象池
→ MotionEvent.obtain() — 触摸事件对象池
→ 同一设计思想:高频创建的短生命周期对象,用池化减少GC
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 6.8 EventBusBuilder建造者模式解析
public class EventBusBuilder {
// 默认线程池
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE =
Executors.newCachedThreadPool();
boolean logSubscriberExceptions = true;
boolean logNoSubscriberMessages = true;
boolean sendSubscriberExceptionEvent = true;
boolean sendNoSubscriberEvent = true;
boolean throwSubscriberException; // 默认false
boolean eventInheritance = true;
boolean ignoreGeneratedIndex; // 默认false
boolean strictMethodVerification; // 默认false
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
List<Class<?>> skipMethodVerificationForClasses;
List<SubscriberInfoIndex> subscriberInfoIndexes;
Logger logger;
MainThreadSupport mainThreadSupport;
// 链式配置
public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
this.logNoSubscriberMessages = logNoSubscriberMessages;
return this; // 返回自身,支持链式调用
}
public EventBusBuilder executorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}
public EventBus build() {
return new EventBus(this); // 将Builder传给EventBus构造函数
}
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists.");
}
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Builder模式在EventBus中的价值:
1. 解决构造函数爆炸问题
EventBus有十几个配置项
→ 如果用构造函数:new EventBus(true, false, true, false, ...)
→ 参数含义不清晰,容易传错
→ Builder让每个配置都有语义化名称
2. installDefaultEventBus()的单例保护
→ synchronized保证线程安全
→ 如果defaultInstance已存在,抛异常
→ 防止多次安装导致配置不一致
→ 建议在Application.onCreate()中调用
3. DEFAULT_EXECUTOR_SERVICE的选择
使用CachedThreadPool而非FixedThreadPool
→ CachedThreadPool特性:
空闲线程自动回收(60秒)
按需创建新线程
适合短时间大量小任务
→ 与EventBus的使用场景匹配:
事件处理通常是短暂的
事件到达时间不确定
不需要固定数量的线程
4. 可替换线程池的扩展设计
executorService(customExecutor)
→ 用户可以传入自定义线程池
→ 例如使用共享线程池减少总线程数
→ 或使用受限线程池控制并发度
→ 开放扩展,对修改封闭(开闭原则)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 6.9 Subscription的active标记设计
Subscription的active标记是一个精巧的并发控制设计:
final class Subscription {
final Object subscriber;
final SubscriberMethod subscriberMethod;
volatile boolean active; // 注意volatile修饰
Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
active = true;
}
}
设计场景分析:
场景:线程A正在遍历subscriptions执行事件分发
同时线程B调用unregister()
不使用active标记的问题:
线程B unregister → 从CopyOnWriteArrayList移除subscription
但线程A遍历的是旧数组(COW快照)→ subscription仍在旧数组中
→ 线程A可能调用已销毁Activity的方法 → Crash
使用active标记后:
线程B unregister → 先设置subscription.active = false
→ 然后从列表移除
线程A分发事件时:
void invokeSubscriber(PendingPost pendingPost) {
...
if (subscription.active) { // 检查active
invokeSubscriber(subscription, event);
}
}
→ 即使线程A遍历到该subscription,active已经是false
→ 不会执行已取消的订阅者的方法
为什么active用volatile修饰?
→ 线程B设置active=false
→ 线程A需要立即看到这个变化
→ volatile保证了跨线程的可见性
→ 不需要synchronized(只有boolean的读写,本身是原子的)
这是一种轻量级的并发安全设计:
→ 不需要重量级的锁
→ 利用volatile的可见性语义
→ 配合CopyOnWriteArrayList的快照遍历
→ 实现了无锁的安全取消订阅
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 6.10 设计思想总结
EventBus的核心设计思想归纳:
1. 读写分离思想
CopyOnWriteArrayList → 读多写少的最佳选择
POST遍历(读)远多于register/unregister(写)
→ 读操作完全无锁,写操作才加锁
2. 空间换时间思想
METHOD_CACHE → 缓存反射结果,避免重复扫描
APT编译时索引 → 编译时多做工作,运行时零开销
PendingPost对象池 → 复用对象,减少GC
3. 线程隔离思想
ThreadLocal<PostingThreadState> → 每线程独立状态
→ 避免全局锁,实现无竞争的并发分发
→ 是EventBus高性能的关键设计
4. 时间片调度思想
HandlerPoster的maxMillisInsideHandleMessage
→ 批量处理事件但不阻塞UI
→ 类似OS的时间片轮转
→ 在吞吐量和响应性之间取得平衡
5. 懒加载思想
APT索引 → init时不加载,register时才查找
METHOD_CACHE → 首次register某类型时才扫描
→ 按需加载,减少不必要的开销
6. 防御性设计
active标记 + volatile → 安全的取消订阅
eventQueue → 防递归栈溢出
isPosting → 防重入分发
每一个设计都考虑了边界场景
7. 对象池化思想
PendingPost对象池 → 减少短生命周期对象创建
FindState对象池 → 减少方法查找过程中的GC
→ 在Android这种GC敏感的环境中尤为重要
8. 开放扩展的设计
EventBusBuilder → 可配置线程池、日志、异常处理
SubscriberInfoIndex → 可插拔的APT索引
→ 框架提供默认行为,用户可按需定制
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 07.EventBus使用注意事项
# 7.1 常见的坑
EventBus使用常见问题:
1. 忘记取消注册 → 内存泄漏
Activity/Fragment中register后必须在onDestroy中unregister
否则EventBus持有订阅者引用 → 内存泄漏
2. 订阅方法不是public → 报错
@Subscribe方法必须是public
private/protected/default都不行
3. 事件类型匹配问题
post(new ChildEvent()) 也会触发 onEvent(ParentEvent e)
因为eventInheritance默认为true
4. 线程模型选择错误
POSTING模式中执行耗时操作 → 阻塞事件分发
MAIN模式中执行耗时操作 → ANR
BACKGROUND模式中更新UI → Crash
5. 粘性事件残留
postSticky后事件会一直保留
新页面打开时收到过期的粘性事件
需要手动removeStickyEvent()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 7.2 EventBus与其他方案对比
组件通信方案对比:
| 特性 | EventBus | LiveData | RxBus | Flow |
|---------------|---------------|---------------|---------------|---------------|
| 生命周期感知 | 不支持 | 支持 | 不支持 | 支持 |
| 线程切换 | 内置四种模式 | 自动主线程 | 需手动指定 | 需手动指定 |
| 数据类型 | 任意对象 | 泛型指定 | 任意对象 | 泛型指定 |
| 粘性事件 | 支持 | 默认粘性 | 需自行实现 | 需自行实现 |
| 优先级 | 支持 | 不支持 | 不支持 | 不支持 |
| 编译检查 | 弱(运行时) | 强(编译时) | 弱 | 强 |
| 调试难度 | 高 | 低 | 高 | 中 |
| 推荐度 | 传统项目 | Jetpack项目 | RxJava项目 | 协程项目 |
现代Android开发趋势:
→ EventBus逐渐被LiveData/Flow替代
→ LiveData/Flow具有生命周期感知,更安全
→ 但EventBus在跨模块通信中仍有价值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 08.面试高频问题深度解析
# 8.1 经典面试题
问题1:EventBus的工作原理是什么?
核心原理:发布/订阅模式 + 反射调用
注册过程:
1. 扫描订阅者中所有@Subscribe注解的方法
2. 以事件类型为Key,将<订阅者+方法>存入Map
3. 以订阅者为Key,将所有事件类型存入Map
发送过程:
1. 根据事件的Class类型从Map中找到所有订阅者
2. 根据ThreadMode将事件分发到对应线程
3. 通过反射调用订阅者的方法
取消注册:
1. 根据订阅者找到所有事件类型
2. 从每种事件类型的订阅者列表中移除该订阅者
2
3
4
5
6
7
8
9
10
11
12
13
14
15
问题2:EventBus的线程切换是如何实现的?
四种ThreadMode的实现:
POSTING:直接反射调用,不切换线程
MAIN:
主线程 → 直接调用
非主线程 → HandlerPoster(使用主线程Handler.sendMessage)
BACKGROUND:
非主线程 → 直接调用
主线程 → BackgroundPoster(线程池串行执行)
ASYNC:
无论什么线程 → AsyncPoster(线程池并行执行)
关键组件:
HandlerPoster → 基于MainLooper的Handler
BackgroundPoster → 单线程串行执行器
AsyncPoster → CachedThreadPool并行执行器
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
问题3:EventBus的粘性事件原理?
粘性事件原理:
发送时:
stickyEvents.put(event.getClass(), event);
→ 保存到Map中(每种类型只保留最后一个)
→ 然后执行普通post
注册时:
如果subscriberMethod.sticky == true
→ 从stickyEvents中查找该eventType的事件
→ 如果存在 → 立即分发给该订阅者
使用场景:
页面跳转传递数据,先发事件,后注册接收
服务状态通知,新UI组件创建后立即获取最新状态
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8.2 进阶面试题
问题4:EventBus 3.0的APT优化原理?
编译时优化:
→ EventBusAnnotationProcessor在编译期扫描@Subscribe
→ 生成索引类(包含所有订阅方法的信息)
→ 运行时直接查表,避免反射扫描
性能提升:
→ 反射扫描:需要遍历类的所有方法,性能差
→ APT索引:HashMap.get(),O(1)查找
→ 首次注册速度提升约100倍
2
3
4
5
6
7
8
9
问题5:EventBus有什么缺点?为什么现在推荐LiveData/Flow?
EventBus缺点:
1. 不感知生命周期 → 可能在已销毁的Activity中回调 → Crash
2. 事件追踪困难 → 发送和接收分散在不同文件 → 调试困难
3. 类型安全弱 → 事件类型通过运行时匹配 → 不易在编译期发现错误
4. 代码可读性差 → 逻辑跳转不直观 → 维护成本高
5. 容易内存泄漏 → 忘记unregister → 持有订阅者引用
LiveData/Flow优势:
1. 生命周期感知 → 自动在DESTROYED时移除观察者
2. 编译时类型检查 → 泛型约束
3. 与Jetpack架构组件深度集成
4. 数据流更加直观,便于追踪
建议:
新项目 → 使用LiveData/SharedFlow
老项目 → 逐步替换EventBus
跨模块通信 → EventBus仍有适用场景
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17



