1.简介
(1)EventBus是一款发布订阅式的事件总线框架,适用于Android和Java开发。通过EventBus能更方便的进行Android组件间通信,使订阅者和事件发布者解耦,让代码看上去更简洁。部分特点:基于运行时注解、支持订阅优先级、事件发布线程切换、粘性事件等。
(2)Eventbus架构
Eventbus的发布订阅本质上就是观察者模式,也可以简单理解为接口回调(被观察者持有观察者的接口引用,触发时被观察者直接调用观察者的接口引用,如上图中间的Eventbus即充当了引用持有者的角色,将发布者和接收者进行了解耦)
未使用Eventbus的组件间通信 | 使用Eventbus后的组件间通信 |
---|---|
可见使用Eventbus后组件间已完全解耦,图片出自其PPTEventBus3 slides
(3)订阅速度比较
Eventbus3从2015年开始发布,采用编译时注解寻找接收消息的方法,需要手动开启编译时注解,否则默认使用反射的方式寻找。Eventbus3之前采用的是固定的方法名字接收。
下图是在Nexus 9(M)机型测试的每秒订阅次数,包括Otto、Eventbus的不同版本,以及Eventbus3使用编译时注解和反射分别的速度,可见使用编译时注解生成的方法index比其它方式有明显速度优势,表格中越高表示注册速度越快,不过Eventbus3需要手动开启编译时注解,同时配置注解库才能使用,不过对于大多数场景订阅数并不多,使用默认反射策略基本不受影响。
出自EventBus 3 beta announced at droidcon
2.使用方式
- (1)导入Eventbus依赖,可导入注解处理器以使用编译时注解生成订阅者方法索引
- (2)定义消息类型
- (3)准备订阅者,声明注解和接收方法
- (4)发送消息
(1) implementation("org.greenrobot:eventbus:3.3.1")
(2) public static class MessageEvent { // xxx filed }
(3) @Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessageEvent(MessageEvent event) {
// receieve event
}
EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);
(4) EventBus.getDefault().post(new MessageEvent());
- 使用注解生成方法索引方式加快完成方法查找完成订阅
官网指导:
https://greenrobot.org/eventbus/documentation/subscriber-index/
步骤:
工程的build.gradle引入classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
模块的build.gradle引入
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg('eventBusIndex', 'com.chris.twain.MyEventBusAppIndex') // 需包含包名
}
}
dependencies {
implementation "org.greenrobot:eventbus:3.3.1"
kapt "org.greenrobot:eventbus-annotation-processor:3.3.1"
}
然后rebuild工程后即可在build目录下生成对应的方法索引代码,使用注解处理器生成的方法索引需要导入注解处理器并自行通过builder模式build Eventbus对象
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusAppIndex()).build();
3.注册
存储订阅关系的数据结构:
/**
* 按照事件类型分类的订阅方法 HashMap
* key:Class<?> 事件类的 Class 对象, value:CopyOnWriteArrayList<Subscription> 订阅者方法包装类集合
*/
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/**
* 订阅者所接收的事件类型
* key:Object 订阅者, value:List<Class<?>> 所接收的事件类型的 Class 对象 List
*/
private final Map<Object, List<Class<?>>> typesBySubscriber;
入参为订阅者对象,用于寻找订阅者对象、接收消息的方法和事件类型
故有订阅者对象、方法和接收的事件类型组合成的Map
一个订阅者对象可接收多个消息类型,故有消息类型与订阅者的Map
// EventBus.java
public void register(Object subscriber) {
// 通过反射获取Android Lopper类来确定当前是否是Android平台下调用,若在Android下则判断当前库是否依赖了Android相关兼容代码,
// 否则抛出异常,因为Eventbus支持Android平台合纯Java平台
if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
// 满足条件进入此分支后,表示是 Android 平台,但是没有依赖 EventBus 的 Android 兼容库
throw new RuntimeException("It looks like you are using EventBus on Android, " +
"make sure to add the \"eventbus\" Android library to your dependencies.");
}
// 反射获取订阅者的 Class 对象
Class<?> subscriberClass = subscriber.getClass();
// 通过 subscriberMethodFinder 订阅方法查找器去查找订阅者的订阅方法,得到一个订阅方法List<SubscriberMethod>
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 对订阅方法 List 进行遍历
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 遍历到的每一个方法对其产生订阅关系,就是正式存放在订阅者的大集合中
subscribe(subscriber, subscriberMethod);
}
}
}
查找订阅者收消息方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 首先尝试从缓存中获取订阅方法 List
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
// 判断从缓存中获取订阅方法 List 是否为null
if (subscriberMethods != null) {
// 如果不为 null,就返回缓存中的订阅方法 List
return subscriberMethods;
}
// 是否强制使用反射查找,通过此标志位判断,默认false,可手动通过builder构造Eventbus对象时设置为ture
if (ignoreGeneratedIndex) {
// 忽略索引的情况下,通过反射进行查找订阅者方法
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 通过索引的方式进行查找
subscriberMethods = findUsingInfo(subscriberClass);
}
// 如果没有订阅者方法,就抛出 EventBusException 异常
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;
}
}
默认EventBus.getDefault()获取的Eventbus对象将采用反射的方式运行时获取订阅消息接收的方法
通过配置注解处理器则将调用findUsingInfo()方法读取编译时注解处理器生成的订阅消息接收方法的索引,使方法查找更高效
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
// 获取一个 FindState 对象
FindState findState = prepareFindState();
// 为当前订阅者初始化 FindState
findState.initForSubscriber(subscriberClass);
// 循环 从子类到父类查找订阅方法
while (findState.clazz != null) {
// 获取索引类并赋值给 findState.subscriberInfo
findState.subscriberInfo = getSubscriberInfo(findState);
// 当索引类的信息类不为 null 时,进一步操作
if (findState.subscriberInfo != null) {
// 获取索引类的所有订阅者方法
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
// 对订阅者方法数组遍历
for (SubscriberMethod subscriberMethod : array) {
// 检查并将方法添加
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
// 校验通过,将该订阅方法添加至 subscriberMethods,
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
// 如果没有索引类,就使用反射的方式继续查找
findUsingReflectionInSingleClass(findState);
}
// 将 findState 的 Class 对象移至父类型
findState.moveToSuperclass();
}
// 返回查找到的订阅者方法并释放资源
return getMethodsAndRelease(findState);
}
寻找完消息接收方法后,完成订阅
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 获取订阅者方法接收的事件类型 Class 对象
Class<?> eventType = subscriberMethod.eventType;
// 创建 Subscription
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 从 subscriptionsByEventType 中 尝试获取当前订阅方法接收的事件类型的值
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
// 如果为 null,表示该方法是第一个,创建空的CopyOnWriteArrayList put 进 subscriptionsByEventType
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
// 如果不为 null,判断现有数据中是否存在该方法,如果存在抛出 EventBusException 异常
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++) {
// 这一步主要是将订阅者方法添加进 subscriptionsByEventType 数据中,并且会按照优先级进行插入
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);
// 对黏性事件进行处理
if (subscriberMethod.sticky) {
// 是否事件继承
if (eventInheritance) {
// 必须考虑所有 eventType 子类的现有粘性事件。
// Note: 迭代所有事件可能会因大量粘性事件而效率低下,因此应更改数据结构以允许更有效的查找
// (e.g. 存储超类的子类的附加映射: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
// 判断 eventType 是否是 candidateEventType 的父类或父接口
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
// 如果是父子关系 进行事件检查和发布
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
// 从黏性事件 Map 中获取当前事件类型的最新事件
Object stickyEvent = stickyEvents.get(eventType);
// 校验事件并发布事件
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
4.消息/事件发布
public void post(Object event) {
// 从当前线程中取得线程专属变量 PostingThreadState 实例,ThreadLocal实现
PostingThreadState postingState = currentPostingThreadState.get();
// 拿到事件队列
List<Object> eventQueue = postingState.eventQueue;
// 将事件入队
eventQueue.add(event);
// 判断当前线程是否在发布事件中
if (!postingState.isPosting) {
// 设置当前线程是否是主线程
postingState.isMainThread = isMainThread();
// 将当前线程标记为正在发布
postingState.isPosting = true;
// 如果 canceled 为 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;
}
}
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
// 获取事件的Class对象
Class<?> eventClass = event.getClass();
// 订阅者是否找到
boolean subscriptionFound = false;
// 判断是否处理事件继承
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) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
// 判断事件无匹配订阅函数时,是否发布 NoSubscriberEvent
if (sendNoSubscriberEvent
&& eventClass != NoSubscriberEvent.class
&& eventClass != SubscriberExceptionEvent.class) {
// 发布 NoSubscriberEvent
post(new NoSubscriberEvent(this, event));
}
}
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
// 加锁 监视器为当前对象
synchronized (this) {
// 获取该 Class 对象的订阅方法 List
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
// 如果存在订阅方法,就进行遍历操作
for (Subscription subscription : subscriptions) {
// 将事件和订阅方法赋值给 postingState
postingState.event = event;
postingState.subscription = subscription;
// 是否中止
boolean aborted;
try {
// 将事件发布到订阅者
postToSubscription(subscription, event, postingState.isMainThread);
// 是否已经取消发布
aborted = postingState.canceled;
} finally {
// 重置 postingState 状态
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
// 如果已经中止,就跳出循环
// TODO: 2022/5/27 为什么不在开始处判断? 还要发布一次后再取消?
if (aborted) {
break;
}
}
// 方法体结束,返回找到订阅关系
return true;
}
// 到此步骤表示没有订阅方法,返回 false
return false;
}
5.消息接收
EventBus五种消息接收的线程模式
// ThreadMode.java
public enum ThreadMode {
/**
* Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
* implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
* simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
* using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
消息接收与发送处于同一线程
*/
POSTING,
/**
* On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
* the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
* is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
* If not on Android, behaves the same as {@link #POSTING}.
主线程执行消息接收
*/
MAIN,
/**
* On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
* the event will always be queued for delivery. This ensures that the post call is non-blocking.
同上,但每次都放到主线程Lopper消息队列尾部,确保异步
*/
MAIN_ORDERED,
/**
* On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
* will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
* background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
* return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
确保不在主线程收消息,若发消息在主线程则切到线程池,若发消息在子线程则不切线程
*/
BACKGROUND,
/**
* Subscriber will be called in a separate thread. This is always independent from the posting thread and the
* main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
* use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
* of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
* uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
*/
收消息方法放入线程池中执行
ASYNC
}
// EventBus.java
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 {
// temporary: technically not correct as poster not decoupled from subscriber
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);
}
}
Android平台下有主线程和子线程的5种收消息执行的线程模式,若不在Android下则仅两种,一种收发消息在相同线程,一种是收消息通过线程池切换线程
Eventbus的ASYNC线程模式使用的默认线程池为Executors.newCachedThreadPool(),也可以自定义线程池通过Builder构造Eventbus对象时传入
6.解注册
根据此订阅者接收消息的事件类型和此订阅者逐个从订阅集合中移除
public synchronized void unregister(Object subscriber) {
// 获取订阅者接收的事件类型
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 按事件类型遍历退订相关订阅方法
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
// 从订阅者所接收的事件类型Map中移除该订阅者
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
Caused by: org.greenrobot.eventbus.EventBusException: Subscriber class com.xxxk.MyTestClass and its super classes have no public methods with the @Subscribe annotation
at org.greenrobot.eventbus.SubscriberMethodFinder.findSubscriberMethods(SubscriberMethodFinder.java:67)