【Android】EventBus 的使用
前言
在传统的 Android 开发中,组件间通信通常依赖于 Intent、Handler、BroadcastReceiver 或接口回调等方式。这些方式在某些场景下会变得非常繁琐和难以维护:
- 高耦合:组件之间需要相互持有引用或定义复杂的接口。
- 代码复杂:当需要传递消息的对象很多时,代码会充满各种回调接口和胶水代码,难以阅读和管理。
- 生命周期问题:在 Android 中,组件的生命周期管理非常关键。使用传统方式,很容易在组件(如 Activity)销毁后,仍然收到回调,从而导致空指针异常或内存泄漏。
为了简化并且更加高质量地在 Activity、Fragment、Service 等之间的通信,同时解决组件之间高耦合的同时仍能继续高效地通信,事件总线设计出现了。
EventBus 是一个基于发布-订阅模式 的事件总线库,可以让 Android 应用中的不同组件(如 Activity、Fragment、Service 等)之间进行低耦合、高效的通信。
发布-订阅模式是一种消息传递范式,其中消息的发送者(称为发布者)不会直接将消息发送给特定的接收者(称为订阅者),而是将发布的消息分为不同的类别,订阅者只接收感兴趣的消息类别,而无需知道哪些发布者存在。
EventBus 的使用
在使用之前先了解 EventBus 的三个要素:
- Event(事件):可以是任意类型的对象。
- Subscriber(订阅者) :事件的订阅者,接收并处理事件。在 EventBus 3.0 之前消息处理的方法只能限定于 onEvent、onEventMainThread、onEventBackgroundThread 和 onEventAsync,它们分别代表4种线程模型。而在 EventBus 3.0 之后,事件处理的方法可以随便取名,但是需要添加一个注解
@Subscribe,并且要指定线程模型(默认为POSTING)。 - Publisher(发布者) :事件的发布者。我们可以在任何线程里发布事件,直接调用 EventBus 的
post(Object)方法。可以自己实例化 EventBus 对象,一般使用EventBus.getDefault()就可以。根据 post 函数参数的类型,会自动调用订阅相应类型事件的函数。

添加依赖
java
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
}
混淆配置
如果使用代码混淆,需要在 Proguard 规则文件(proguard-rules.pro)中添加以下配置
java
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# If using AsyncExecutord, keep required constructor of default event used.
# Adjust the class name if a custom failure event type is used.
-keepclassmembers class org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
# Accessed via reflection, avoid renaming or removal
-keep class org.greenrobot.eventbus.android.AndroidComponentsImpl
基本使用
定义事件
创建一个简单的 Java 类来表示你想要传递的数据或通知:
java
public class MessageEvent {
private String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
准备订阅者
在需要接收事件的组件中(如 Activity、Fragment等)中:
-
注册/注销:组件必须在开始接收事件前向 EventBus 注册自己,并在不再需要接收事件(或生命周期结束时)注销自己,以避免内存泄漏和无效调用。
java@Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); // 注册当前 Activity 作为订阅者 } @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); // 注销当前 Activity } -
订阅事件
使用注解
@Subscribe标记一个方法,需要注意的是,这里的方法名是任意的,但必须是公共方法(public),且只能有一个参数。该方法的参数决定它订阅哪种事件。java@Subscribe public void onMessageEvent(MessageEvent event) { String message = event.getMessage(); // 处理接收到的事件 }
发布事件
在任何需要通知其他组件的地方,获取 EventBus 实例并调用post(Object event)方法,event对象就是你要传递的事件实例。
java
EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
@Subscribe 注解
@Subscribe 是 EventBus 提供的一个注解,用来标记 订阅事件的方法。源码如下,有三个注解属性:
java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
线程模式(ThreadMode)
这是最重要的一个属性,决定了订阅方法在哪个线程被调用。
EventBus 提供 5 种模式:
POSTING:默认 的最小开销处理方式,不进行线程切换,在哪个线程发送事件,就在哪个线程处理事件。- 如果使用该模式,不要处理耗时事件,否则会长时间阻塞发布事件的线程;
- 适合轻量级逻辑。
- 主线程发 → 主线程回调
- 子线程发 → 子线程回调
MAIN:回调一定在主线程( UI 线程),如果在子线程发送消息,处理消息时会将线程切换成主线程。- 如果发送事件的线程是主线程,则立刻调用消息处理事件,此时主线程会阻塞;
- 如果发送事件的线程是子线程,事件在队列中排队等待传递,不会阻塞发布线程;
- 适合UI更新。
MAIN_ORDERED:与 MAIN 类似,但事件会按队列排队执行,避免阻塞主线程。避免频繁事件卡 UI。BACKGROUND:子线程模式。- 在主线程中发布事件,会将事件加入队列中,然后通过线程池执行 ;
- 在子线程中发布事件,直接在该线程中调用事件处理方法,会阻塞发布线程 ;
- 适合轻量非 UI 任务。
ASYNC:不管在哪个线程发布事件,都会将事件放入队列,通过线程池执行事件。- 不共享线程 → 适合耗时任务(网络、IO)
以下是源码及注释:
java
package org.greenrobot.eventbus;
/**
* 每个订户方法都有一个线程模式,该模式确定EventBus将在哪个线程中调用该方法。
* EventBus独立于发布线程处理线程。
*
* @see EventBus#register(Object)
* @author Markus
*/
public enum ThreadMode {
/**
* 订阅服务器将在发布事件的同一线程中直接调用。
* 这是默认设置。
* 事件传递意味着开销最小,因为它完全避免了线程切换。
* 因此,对于已知可以在很短时间内完成而不需要主线程的简单任务,这是推荐的模式。
* 使用此模式的事件处理程序必须快速返回,以避免阻塞发布线程(可能是主线程)。
*/
POSTING,
/**
* 在Android上,订户将在Android的主线程(UI线程)中被调用。
* 如果发布线程是主线程,则将直接调用订阅者方法,从而阻塞发布线程。
* 否则,事件将排队等待传递(非阻塞)。使用此模式的订阅服务器必须快速返回以避免阻塞主线程。
* 如果不在Android上,其行为与{@link#POSTING}相同。
*/
MAIN,
/**
* 在Android上,订户将在Android的主线程(UI线程)中被调用。
* 与{@link#MAIN}不同,事件将始终排队等待传递。这确保post调用是非阻塞的。
*/
MAIN_ORDERED,
/**
* 在Android上,订阅者将在后台线程中被调用。
* 如果发布线程不是主线程,则将在发布线程中直接调用订阅方方法。
* 如果发布线程是主线程,EventBus将使用一个后台线程,该线程将按顺序传递其所有事件。
* 使用此模式的订阅者应尝试快速返回,以避免阻塞后台线程。
* 如果不在Android上,则始终使用后台线程。
*/
BACKGROUND,
/**
* 订户将在单独的线程中被调用。
* 这始终独立于发布线程和主线程。
* 发布事件从不等待使用此模式的订阅服务器方法。
* 如果订户方法的执行可能需要一些时间,例如网络访问,则订户方法应使用此模式。
* 避免同时触发大量长时间运行的异步订阅服务器方法,以限制并发线程的数量。
* EventBus使用线程池高效地重用已完成异步订户通知中的线程。
*/
ASYNC
}
sticky(是否处理粘性事件)
粘性事件是指发布后被缓存的事件,即使在事件发布之后才注册订阅者,新的订阅者仍能收到最近发布过的那条(或那些)事件。
sticky默认为false表示不处理粘性事件。
使用方式:
java
// 发送(缓存)
EventBus.getDefault().postSticky(event);
// 订阅:注解标记
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onLogin(UserLoginEvent e) { ... }
// 获取已缓存的粘性事件(不通过订阅)
UserLoginEvent e = EventBus.getDefault().getStickyEvent(UserLoginEvent.class);
// 移除某个类型的粘性事件(返回被移除的实例)
EventBus.getDefault().removeStickyEvent(UserLoginEvent.class);
// 移除某个具体实例
EventBus.getDefault().removeStickyEvent(specificEvent);
// 清空所有粘性事件
EventBus.getDefault().removeAllStickyEvents();
需要注意,使用黏性事件时要谨慎处理内存管理问题,避免事件过多导致内存泄漏或占用过多内存的情况,在使用后要及时 remove。
priority(订阅者优先级)
可以通过priority属性来设置订阅者方法的优先级。优先级决定了订阅者方法在事件发布时的执行顺序。
优先级是一个整数值,数值越小,优先级越高。默认情况下,优先级为0,所有订阅者方法的优先级相同。当设置priority属性时,数值越小,优先级越高。
如果优先级相同的订阅者方法存在,EventBus会根据注册的顺序来确定执行顺序。先注册的订阅者方法会先执行。
仅在 同一事件类型 的多个订阅者之间有效。
在 POSTING 模式下,高优先级订阅者可以调用:
java
EventBus.getDefault().cancelEventDelivery(event);
这样后续优先级更低的订阅者 不会收到该事件。该方法仅在 POSTING 模式下有效。
注意:在异步或跨线程模式下,虽然分发顺序仍按优先级,但执行完成的时间不一定按优先级,因此不要在跨线程场景依赖顺序。