观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

  不推荐使用EventBus框架,此框架用的随意将是灾难,介于一开始接触的人都不可能很正规的使用它。所以,推荐了解它,但是不使用它。为什么需要了解它是因为别人可能会使用这个,并且在一些特殊情况下EventBus的确有自己的优势。

介绍

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

优点

  • 简单统一数据传递
  • 有粘性消息机制,保证了消息的接收的必达
  • 可以选择的主次线程接收消息,无需写大量代码切换线程
  • 使用实体类传递数据且无需序列化,摆脱了用Bundle以Key:Value形式传递字段,序列化实体类
  • 在activity与activity,或者Service与activity传递大数据时的选择之一。因为序列化大数据进行传递时,是十分耗时缓慢的。用EventBus比序列化更快。

缺点

  • 滥用它,EventBus可以大量解耦项目,但是如果你大量的使用它会产生一个非常危险的后果,你需要定义大量的枚举类或者新的实体类来区分接收者。管理EventBus的消息类别将会你的痛苦
  • 不刻意写大量实体类与枚举类代码来管理事件的收发,那么在大量使用后你就会对着EventBus每天在玩找蛇头与蛇尾的游戏。
  • 在非前台组件中使用它,不只在Activity或者Service,广播里使用它。 而是将它使用到每一个工具类 或者 后台业务类,除了让数据发送与接收更加复杂。别忘记了Java本身就是面对对象语言,它有接口、抽象可以实现来发送与接收数据。你可以用各种设计模式,比如观察者模式,来更好的优化与独立自己的业务。不需要依赖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,发布事件并返回。第一次的时候,这里的两个订阅方法都会被触发。然后,我们按下停止分发的按钮,并再次执行上面的逻辑,此时只有优先级较高的方法获取到了事件并将该事件终止。

posted on 2018-08-27 21:00  观心静  阅读(581)  评论(0编辑  收藏  举报