Android 开发 框架系列 EventBus 事件总线

时间:2023-03-09 07:09:16
Android 开发  框架系列 EventBus 事件总线

介绍

GitHub:https://github.com/greenrobot/EventBus

先聊聊EventBus 线程总线是干什么的,使用环境,优点、缺点。

干什么的?

一句话,简单统一数据传递 和 提供主次多个线程

  • 数据传递:Android系统有很多类别的数据传递方式,例如Intent 活动之间传递数据、Message与Handler 主次线程之间传递数据、广播的方式、使用基类危险的去传递数据。传递数据的方式太多,且都需要各自的注册方式,使用使用起来比较繁琐。所以EventBus第一个用处是比较统一的去传递数据,特别是碎片、服务、活动,UI线程与子线程 之间的传递,可以有一个比较统一的方式。
  • 线程:有了数据就需要逻辑去处理它,相比android系统的数据传递后需要手动创建线程去处理逻辑后,在将得到的数据传到主线程,这一套繁复且反复的传递的过程。EventBus可以很直接的在对应线程方法里去直接互相传递处理。

使用环境

多个地方需要传递数据和相应线程逻辑处理环境,比如碎片+上下活动+服务+广播+线程的情况下,你可以想象一下为了这些数据处理需要敲不少的代码。EventBus 线程可以统一的管理,只需要在对应类里注册它。

优点

  • 简单统一数据传递
  • 清晰明了的主次线程
  • 使用class传递数据(是的,最好用的地方用一个class来传递数据,这下传一个class,就可以携带各种各样的数据了,摆脱了用Bundle传递list和数组简直太爽了
  • 在activity与activity,或者Service与activity传递大数据时的唯一选择。因为序列化大数据进行传递时,是十分耗时缓慢的。用EventBus是最优解法。

缺点

  • 滥用它,EventBus可以大量解耦项目,但是如果你大量的使用它会产生一个非常危险的后果,你需要定义大量的常量或者新的实体类来区分接收者。管理EventBus的消息类别将会你的痛苦
  • 在非前台组件中使用它,不只在Activity或者Service,广播里使用它。 而是将它使用到每一个工具类 或者 后台业务类,除了让数据发送与接收更加复杂。别忘记了Java本身就是面对对象语言,它有接口、抽象可以实现来发送与接收数据。你可以用各种设计模式,比如观察者模式,来更好的优化与独立自己的业务。不需要依赖EventBus。
  • EventBus,并不是真正的解耦。请不要在你独立的模块里使用EventBus来分发。你这个模块如果那天要直接放入另外一个项目里,你怎么解耦EventBus? 最好,还是多使用接口与Activity本身的数据传递方式。

使用方式:

1.添加依赖

    implementation 'org.greenrobot:eventbus:3.1.1'

2.添加注册

 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_bus_demo); EventBus.getDefault().register(this);//注册 }

3.注销

@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}

4.传递数据准备,创建数据类

public class MsgData {
private String msg;
public MsgData(String msg){
this.msg = msg;
}
public String getmsg() {
return msg;
}
}

5.注册发送事件与发送数据

  EventBus.getDefault().post(new MsgData("send"));//注册,发送的是上面创建的数据class

注意!这里说明一下,如果只需要post发送(这个class只需要发送事件,不需要接收事件),不需要在class做初始化和销毁的操作。直接调用post就可以了

6.处理接收事件与线程

上面5个步骤都很简单,接收与线程稍微有一些需要解释:

4种线程模式:

  • POSTING:Event处理函数在发布Event的线程中执行。(ANR)
  • MAIN:Event处理函数在UI线程中执行。(ANR)
  • BACKGROUND:如果发布Event的线程是UI线程,Event处理函数就新建子线程中执行;如果发布Event的线程是子线程,那么就在当前子线程执行。(不允许修改UI)
  • ASYNC:新建子线程执行Event处理函数,新建的线程与UI线程八竿子打不着。(所以别在这里处理UI)

创建上面四种线程方法:

这些线程不是需要抽象重写或者接口的重写方法,需要自己创建一个方法,使用注释@Subscribe添加自己需要的线程模式,

下面就是创建了UI主线程方法的例子:

  @Subscribe(threadMode = ThreadMode.MAIN,sticky = false)
public void onUiMain(MsgData msgData){
if (msgData.getmsg().equals("send")) { //得到发送过来的数据
mContent.setText("接收到了数据");
}
}
注意!接收事件的方法一定需要做初始化和销毁。

7.粘性事件

黏性事件,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。粘性事件与普通事件创建一样,只是有细微不同。

发送粘性事件:

MsgData msgData = new MsgData("我是粘性数据");
EventBus.getDefault().postSticky(msgData);//注意,这里使用的是postSticky发送粘性事件

接收粘性事件:

  @Subscribe(threadMode = ThreadMode.MAIN,sticky = true) //重点,这里的sticky 设置为了true
public void onUiMain(MsgData msgData){
if (msgData.getmsg().equals("我是粘性数据")) {
mContent.setText("接收粘性数据");
}
}

粘性事件会一直保存在内存中,等待后续的接收粘性事件方法的创建与接收,因为会一直停留在内存里,所以我们需要去清理粘性事件,粘性事件接收到了确定无用后就清理是个好习惯。

  • 移除指定的粘性事件:removeStickyEvent(Object event);
  • 移除指定类型的粘性事件:removeStickyEvent(Class<T> eventType);
  • 移除所有的粘性事件:removeAllStickyEvents();
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void onUiMain(MsgData msgData){
if (msgData.getmsg().equals("粘性数据")) {
mContent.setText("接收到了数据");
}
if(EventBus.getDefault().removeStickyEvent(msgData)){
Toast.makeText(this,"清理粘性事件成功",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(this,"清理粘性事件失败",Toast.LENGTH_SHORT).show();
}
}

关于粘性事件接收不到事件坑,请一定注意

有一下条件下,会出现一个坑:

1.如果你使用一个泛型的类作为App全局的EventBus消息类时
2.如果一个类里及接受粘性事件又发送粘性事件
3.你当前发送类里的在接收方法里没有判断EventBus消息类的key,就执行了清理粘性事件
有以上条件,你会发现你在这个类里发出去的粘性事件,在目标类的接收方法里接收不到? 原因就是你没有判断EventBus消息类的key就去执行清理粘性事件,在自己当前类的发送方法里又接收到自己发的粘性事件,然后又去清理掉了.

优先级

Subscribe注解中总共有3个参数,上面我们用到了其中的两个,这里我们使用以下第三个参数,即priority。它用来指定订阅方法的优先级,是一个整数类型的值,默认是0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。

为了对优先级进行测试,这里我们需要对上面的代码进行一些修改。这里,我们使用一个布尔类型的变量来判断是否应该取消事件的分发。我们在一个较高优先级的方法中通过该布尔值进行判断,如果未true就停止该事件的继续分发,从而通过低优先级的订阅方法无法获取到事件来证明优先级较高的订阅方法率先获取到了事件。

这里有几个地方需要注意

  1. 只有当两个订阅方法使用相同的ThreadMode参数的时候,它们的优先级才会与priority指定的值一致;
  2. 只有当某个订阅方法的ThreadMode参数为POSTING的时候,它才能停止该事件的继续分发。

所以,根据以上的内容,我们需要对代码做如下的调整:

// 用来判断是否需要停止事件的继续分发
private boolean stopDelivery = false; @Override
protected void doCreateView(Bundle savedInstanceState) {
// ... getBinding().btnStop.setOnClickListener(v -> stopDelivery = true);
} @Subscribe(threadMode = ThreadMode.POSTING, priority = 0)
public void onGetMessage(MessageWrap message) {
getBinding().tvMessage.setText(message.message);
} // 订阅方法,需要与上面的方法的threadMode一致,并且优先级略高
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 1)
public void onGetStickyEvent(MessageWrap message) {
String txt = "Sticky event: " + message.message;
getBinding().tvStickyMessage.setText(txt);
if (stopDelivery) {
// 终止事件的继续分发
EventBus.getDefault().cancelEventDelivery(message);
}
}

即我们在之前的代码之上增加了一个按钮,用来将stopDelivery的值置为true。该字段随后将会被用来判断是否要终止事件的继续分发,因为我们需要在代码中停止事件的继续分发,所以,我们需要将上面的两个订阅方法的threadMode的值都置为ThreadMode.POSTING

按照,上面的测试方式,首先我们在当前的Activity注册监听,然后跳转到另一个Activity,发布事件并返回。第一次的时候,这里的两个订阅方法都会被触发。然后,我们按下停止分发的按钮,并再次执行上面的逻辑,此时只有优先级较高的方法获取到了事件并将该事件终止。