【Android】EventBus 的使用

【Android】EventBus 的使用

前言

在传统的 Android 开发中,组件间通信通常依赖于 Intent、Handler、BroadcastReceiver 或接口回调等方式。这些方式在某些场景下会变得非常繁琐和难以维护:

  1. 高耦合:组件之间需要相互持有引用或定义复杂的接口。
  2. 代码复杂:当需要传递消息的对象很多时,代码会充满各种回调接口和胶水代码,难以阅读和管理。
  3. 生命周期问题:在 Android 中,组件的生命周期管理非常关键。使用传统方式,很容易在组件(如 Activity)销毁后,仍然收到回调,从而导致空指针异常或内存泄漏。

为了简化并且更加高质量地在 Activity、Fragment、Service 等之间的通信,同时解决组件之间高耦合的同时仍能继续高效地通信,事件总线设计出现了。

EventBus 是一个基于发布-订阅模式 的事件总线库,可以让 Android 应用中的不同组件(如 Activity、Fragment、Service 等)之间进行低耦合、高效的通信。

发布-订阅模式是一种消息传递范式,其中消息的发送者(称为发布者)不会直接将消息发送给特定的接收者(称为订阅者),而是将发布的消息分为不同的类别,订阅者只接收感兴趣的消息类别,而无需知道哪些发布者存在。

EventBus 的使用

在使用之前先了解 EventBus 的三个要素:

  1. Event(事件):可以是任意类型的对象。
  2. Subscriber(订阅者) :事件的订阅者,接收并处理事件。在 EventBus 3.0 之前消息处理的方法只能限定于 onEvent、onEventMainThread、onEventBackgroundThread 和 onEventAsync,它们分别代表4种线程模型。而在 EventBus 3.0 之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING)。
  3. 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 模式下有效。

注意:在异步或跨线程模式下,虽然分发顺序仍按优先级,但执行完成的时间不一定按优先级,因此不要在跨线程场景依赖顺序。

相关推荐
v***55341 小时前
什么是Spring Boot 应用开发?
java·spring boot·后端
2509_940880221 小时前
Spring Cloud GateWay搭建
android·前端·后端
k***92161 小时前
SpringBoot集成MQTT客户端
java·spring boot·后端
Haha_bj1 小时前
一、Kotlin基础
android·kotlin
The Straggling Crow1 小时前
缓存策略、批推理(batching)、异步 /并发机制
android·缓存
二十雨辰1 小时前
[天机学堂]-01环境搭建
java·spring cloud
h***59331 小时前
JAVA进阶 Thread学习06 synchronized关键字
java·开发语言·学习
j***48541 小时前
【JSqlParser】Java使用JSqlParser解析SQL语句总结
java·开发语言·sql
如意.7591 小时前
【C++】——异常
java·开发语言