综述
消息总线又叫事件总线, 被广泛的应用于各类项目之中. 但是此处只概述Android体系中用到的框架. 为什么项目会需要一个消息总线呢? 一句话概括, 在大多数常见项目中, 随着项目变大, 项目可能出现大量的跨页面, 跨组件, 跨线程, 跨进程来传递消息与数据的需求. 为了更方便的直接通知到指定的页面实现具体的逻辑, 工程师们发展了消息总线体系来解决这一类问题, 因此如今我们可以在其它工程师的工作基础上简单的实现我们想要的功能.
现在是2023年, 从现在往前推10年, 这期间Android体系中被广泛使用的主流消息总线框架大致有如下几种:
- 首先是Android原生的 Broadcast 以及 BroadcastReceiver 体系. 这是Android中最原始也最笨重的事件总线.
- 接着是 GreenRobot 公司出品的 EventBus 和开发者们使用 RxJava 库封装的 RxBus 相互竞争. 一者简单易学上限低, 一者强大复杂难入门.
- 然后流行的是使用官方出品的 Android Jetpack 库中的 livedata 组件封装的 LiveDataBus.
- 最新开始广泛使用的消息总线则是使用SharedFlow封装的 FlowBus.
所以, 回到本文的核心, 从某种意义上说, Eventbus处于一个承上启下的位置. 相较于原生的广播体系它简化了组件间的通信, 提高了开发效率, 又因为其流行时间较早而广泛的应用于大量早期开发的android项目中, 没有 RxBus 强大的功能既是缺点也是优点, 因为这样也就没有过于复杂的上手难度. 唯一值得诟病的是消息发送和接收没有一个统一的管理或者配置中心, 一旦大规模使用会非常繁杂, 使得其在大型和超大型项目中难以维护.
因此, 我整理并记录这篇文章, 方便在日后需要时进行回顾.
概念
以下为GreenRobot官方给出的EventBus消息传递示意图
此图描述使用eventbus传递一次消息所涉及的四个角色, 即
- 发布者, 也就是Publisher, 负责发送消息.
- 订阅者, 也就是Subscriber, 负责接收消息.
- 事件, 也就是Event, 即存放信息的事件对象.
- EventBus, 可以理解为中转站, 事件从发布者通过EventBus发送到订阅者那里.
在使用时, 需要工程师自己实现的仅有前三个角色. 中转站本身EventBus库已经自行实现.
使用方法
添加依赖
使用前, 需要引入相关的库, 在gradle中加入
implementation("org.greenrobot:eventbus:3.3.1")
//或者是java版本
implementation("org.greenrobot:eventbus-java:3.3.1")
如果使用的是maven则在依赖中加入
<dependency>
<groupId>org.greenrobot</groupId>
<artifactId>eventbus-java</artifactId>
<version>3.3.1</version>
</dependency>
其它的比如将jar包引入项目的做法此处略过.
简单使用
首先需要定义一个事件Event, 这个类一般单独定义
//java版本
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
然后定义一个订阅者, 这个方法一般会放在需要接受信息的类中, 以下两个示例分别对应使用主线程(UI线程)传递和处理事件, 以及使用当前发布者所在的线程(无论是哪个线程)传递和处理事件时的做法.
//java版本
// This method will be called when a MessageEvent is posted (in the UI thread for Toast)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
doSomethingWith(event);
}
同时, 定义的订阅者方法的类需要被注册才能被eventbus知晓, 在有生命周期回调的类比如fragment或者activity中使用订阅者一般在生命周期中注册和注销订阅者所在的类. 而没有生命周期回调的类比如repo或者datasource, 一般会在初始化时注册订阅者, 在类被主动销毁时注销订阅者(或者干脆不注销, 使用单例模式加上app运行结束系统自动关闭).
注意: 订阅者方法必须为public, 且无论是缺了注册注销还是缺了订阅者方法都可能导致app报错
//java版本
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
最后, 需要定义一个发布者, 发布者非常简单, 只是一条简单的指令, 在任何需要发送数据的地方直接调用即可
//java版本
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
拓展功能介绍
订阅方式
在实现订阅者的时候除了默认的使用 @Subscriber 注解指定订阅者方法的做法之外, 还可以使用拓展功能同时指定消息传输的线程
使用如下方式
//默认方式
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
doSomethingWith(event);
}
//拓展方式
// Called in the same thread (default)
// ThreadMode is optional here
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
log(event.message);
}
// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
- 其中使用 @Subscribe(threadMode = ThreadMode.POSTING) 等同于使用 @Subscribe, 或者说后者就是前者的简写.
- 使用 @Subscribe(threadMode = ThreadMode.MAIN) 表示订阅者方法将在android的主线程即UI线程中被调用, 一般用来处理和UI有关的操作.
- 使用 @Subscribe(threadMode = ThreadMode.MAIN_ORDERED) 将使得事件会严格的按照调用的顺序通过UI线程排队发送给订阅者.
- 使用 @Subscribe(threadMode = ThreadMode.BACKGROUND) 表示如果发布者不是在主线程发布的事件则直接发送给订阅者, 如果发布方法在主线程发布的事件, eventbus将使用后台线程单独发送事件.
- 使用 @Subscribe(threadMode = ThreadMode.ASYNC) 表示EventBus将始终使用另外的单独线程将事件发送给订阅者方法, 无论发布者是在哪个线程发送的事件.
粘性事件
粘性事件是一种特殊的事件, 常规来说, 一般发布者发送出去的事件如果发送后订阅者才注册, 那么新注册的订阅者是不会收到发送者发送的数据. 但是如果处于特殊场景下, 项目需要订阅者在注册后接收注册前已经发出的信息, 就可以使用粘性事件.
考虑以下假想场景:
一个项目需要和远程服务器进行连接并且隔一段时间动态获取数据并更新在activity界面, 所以使用了service进行持续的自动运行.
由于数据更新在ui上, 所以设置为activity处于onResume()时开启service, activity处于onPause()时停止service. 因为有时候用户会需要立刻刷新数据而不是等几十秒再刷新, 所以设置使用eventbus通知service终止当前的循环处理, 重新且立刻开始间隔一段时候就访问一次服务器数据的流程. 同时还存在另一个功能需求, 每次调用均需要动态申请系统权限并获取用户的许可.
好, 假设现在需要在获取用户许可后立刻通知服务器刷新. 那么就会存在获取用户许可时因为调用的是系统窗口, activity处于onstop()回调周期, service停止. 用户点击确定后, service可能已经开启也可能尚未重新初始化并注册订阅者.
所以, 此时通过权限申请回调运行发布者的指令会时不时无效.而在以上场景中, 使用粘性事件会是一种比较好的解决方案.
首先还是需要定义一个事件, 这一点和常规的事件的发送没有区别
//注意, 事件命名在语法上是自由的, 但是为了可读性, 一般会从XXXEvent改为StickyXXXEvent或者XXXStickyEvent
public class StickyMessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
定义一个发布者
//定义发布者
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
最后定义一个接收者
//定义接收者
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
// UI updates must run on MainThread
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
textField.setText(event.message);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
额外的, 因为粘性事件会长期存放在eventbus中, 一般在接收之后需要将其清除
//一般放在onEvent()中获取事件并处理后再执行
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// "Consume" the sticky event
EventBus.getDefault().removeStickyEvent(stickyEvent);
// Now do something with it
}
总结
以上就是eventbus的用法的简单整理, 一般性的使用差不多只需要这些功能. 其它的像是property, configuration, 取消发送之类的功能一般不会用到.
同时需要额外注意, 使用3.0之前的eventbus版本时, 在app打包时, 对eventbus相关方法进行混淆代码可能导致eventbus无法正常工作, 需要在混淆中单独设置规则以避开. 而3.0之后(包括3.0)的eventbus已经采用了新机制, 不需要对此进行额外的处理.