在Android开发中,EventBus作为一款轻量级的事件发布/订阅框架,凭借其简洁的API和高效的线程切换能力,成为组件间通信的热门选择。但很多开发者仅仅停留在基础的post()和@Subscribe注解使用上,忽略了两个极具实用价值的属性——sticky(粘性事件)和priority(优先级)。本文将带你深入这两个属性的实战应用,解决"事件订阅晚了收不到消息"和"多个订阅者处理顺序混乱"这两大典型痛点。
考虑这样一个场景:在ActivityA中发布了一个事件,然后启动ActivityB并注册订阅。按照常规的post()方式,ActivityB将永远收不到这个事件,因为它注册时事件已经发布完毕。这就是典型的"晚到订阅者"问题。
java复制// ActivityA中
EventBus.getDefault().post(new MessageEvent("数据准备完成"));
startActivity(new Intent(this, ActivityB.class));
// ActivityB中
@Subscribe
public void onMessageEvent(MessageEvent event) {
// 这里永远不会执行!
}
粘性事件的实现原理其实很简单:EventBus内部维护了一个Map来存储最近发布的粘性事件。当调用postSticky()时,事件会被存入这个Map;当新的订阅者注册时,如果它的@Subscribe注解中sticky=true,EventBus会检查Map中是否有匹配的事件类型,有则立即投递。
java复制// 修改后的正确用法
// ActivityA中
EventBus.getDefault().postSticky(new MessageEvent("数据准备完成"));
startActivity(new Intent(this, ActivityB.class));
// ActivityB中
@Subscribe(sticky = true)
public void onMessageEvent(MessageEvent event) {
// 现在可以正常接收到事件了
Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
}
注意:粘性事件会一直存在于内存中,记得在不需要时手动移除:
java复制MessageEvent event = EventBus.getDefault().getStickyEvent(MessageEvent.class); if (event != null) { EventBus.getDefault().removeStickyEvent(event); }
当多个订阅者监听同一事件时,默认情况下它们的处理顺序是不确定的。通过priority属性,我们可以像交通信号灯一样精确控制处理顺序——数值越大优先级越高(默认为0)。
java复制@Subscribe(priority = 1)
public void highPriorityEvent(MessageEvent event) {
// 这个会先执行
}
@Subscribe(priority = 0)
public void normalPriorityEvent(MessageEvent event) {
// 这个会后执行
}
优先级并非在所有情况下都有效,它遵循以下规则:
| 条件 | 优先级是否生效 | 说明 |
|---|---|---|
| 相同ThreadMode | ✔️ | 同线程模式下优先级完全有效 |
| 不同ThreadMode | ✖️ | 不同线程模式间不比较优先级 |
| POSTING模式+主线程发布 | ✔️ | 特殊:即使都是POSTING,主线程发布时优先级生效 |
java复制// 示例1:优先级生效的情况
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1)
public void subscriberA(MessageEvent event) {} // 先执行
@Subscribe(threadMode = ThreadMode.MAIN, priority = 0)
public void subscriberB(MessageEvent event) {} // 后执行
// 示例2:优先级不生效的情况
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1)
public void subscriberC(MessageEvent event) {}
@Subscribe(threadMode = ThreadMode.ASYNC, priority = 0)
public void subscriberD(MessageEvent event) {}
// 执行顺序不确定,因为线程模式不同
假设我们需要实现一个登录事件处理流程,要求:
java复制@Subscribe(threadMode = ThreadMode.BACKGROUND, priority = 2)
public void validateParams(LoginEvent event) {
if (TextUtils.isEmpty(event.username)) {
throw new IllegalArgumentException("用户名不能为空");
}
// 校验通过,事件会继续传递
}
@Subscribe(threadMode = ThreadMode.BACKGROUND, priority = 1)
public void checkPermission(LoginEvent event) {
if (!PermissionChecker.hasLoginPermission()) {
EventBus.getDefault().cancelEventDelivery(event);
return;
}
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void doLogin(LoginEvent event) {
// 只有当前面的订阅者都没有取消事件时才会执行
loginAPI.request(event.username, event.password);
}
在MVVM架构中,ViewModel可以成为粘性事件的理想管理者:
java复制public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> liveData = new MutableLiveData<>();
public void updateData(String newData) {
liveData.setValue(newData);
EventBus.getDefault().postSticky(new DataUpdateEvent(newData));
}
// 清理粘性事件
@Override
protected void onCleared() {
EventBus.getDefault().removeStickyEvent(DataUpdateEvent.class);
}
}
在组件化项目中,建议为不同业务模块定义专属事件类,并统一优先级范围:
| 模块 | 优先级范围 | 用途 |
|---|---|---|
| 核心模块 | 1000-1999 | 系统级高优先级事件 |
| 用户模块 | 2000-2999 | 登录/权限相关事件 |
| 订单模块 | 3000-3999 | 交易流程事件 |
| 支付模块 | 4000-4999 | 支付流程事件 |
java复制// 订单模块示例
public class OrderConstants {
public static final int PRIORITY_ORDER_VALIDATION = 3001;
public static final int PRIORITY_ORDER_CREATION = 3000;
}
@Subscribe(priority = OrderConstants.PRIORITY_ORDER_VALIDATION)
public void validateOrder(OrderEvent event) {
// 订单验证逻辑
}
粘性事件最常见的风险是内存泄漏,以下是防护措施:
java复制// 弱引用包装示例
public class BigDataEvent {
private final WeakReference<BigData> dataRef;
public BigDataEvent(BigData data) {
this.dataRef = new WeakReference<>(data);
}
public BigData getData() {
return dataRef.get();
}
}
| 场景 | 推荐ThreadMode | 理由 |
|---|---|---|
| UI更新 | MAIN | 必须主线程操作 |
| 数据库读写 | BACKGROUND | 避免主线程IO |
| 网络请求 | ASYNC | 不阻塞任何线程 |
| 简单计算 | POSTING | 最小开销 |
当事件传递出现问题时,可以通过以下方式调试:
java复制EventBus.builder().logSubscriberExceptions(true).installDefaultEventBus();
java复制@Subscribe
public void onEvent(LogEvent event) {
Log.d("EventBus", "事件传递路径:" +
event.getClass().getSimpleName() + " -> " +
this.getClass().getSimpleName());
}