EventBus使用

概述

关于 EventBus 在开发中经常会选择使用它来进行模块间通信、解耦。平常使用这个库只是很浅显的操作三部曲,register,post,unregister。来达到开发目的。始终有种不明确,模糊的操作感。因此准备对EventBus进行一个深入,全面的理解,消除模糊,片面感,让以后在使用这个库的时候,有更好的掌握和使用。并记录下来,方便以后查阅。关于EventBus会分两章进行记录,本篇文章,是对EventBus的使用做一个全面的介绍,另一篇文章则会对EventBus库的源码进行分析,看看他的实现原理是什么样的。

关于

EventBus 是一个开源库,它利用发布/订阅者者模式来对项目进行解耦。它可以利用很少的代码,来实现多组件间通信。android的组件间通信,我们不由得会想到handler消息机制和广播机制,通过它们也可以进行通信,但是使用它们进行通信,代码量多,组件间容易产生耦合引用。关于EventBus的工作模式,这里引用一张官方图帮助理解。

官网EventBus架构图

上面这张图还是很好理解的,Publisher(发布者)通过post()方法,把Event事件发布出去,Subscriber(订阅者)在onEvent()方法中接收事件,可能现在你对Publisher,Subscriber, onEvent()这几个词还是不理解,没关系,别着急,放下看慢慢的你就懂了。

为什么会选择使用EventBus来做通信?

  • 简化了组件间交流的方式
  • 对事件通信双方进行解耦
  • 可以灵活方便的指定工作线程,通过ThreadMode
  • 速度快,性能好
  • 库比较小,不占内存
  • 使用这个库的app多,有权威性
  • 功能多,使用方便

现在,我们就来体验EventBus的强大之处吧。

使用

导入项目

在build.gradle文件中导入EventBus库。

1
    implementation 'org.greenrobot:eventbus:3.1.1'

在项目的混淆文件中,加入EventBus 的混淆规则,这个千万别忘了,不然会出现,debug版本测试OK,release版本subscriber 收不到消息等诡异问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

使用方式

EventBus库中最重要的三个点,分别是subscriber(订阅者),事件(消息),publisher(发布者)。主要理解这三者的关系即可。

subscriber ——> EventBus 的register方法,传入的object对象

事件(Event)——> EventBus 的post方法,传入的类型。

publisher(发布者)——> EventBus的post方法。


1.创建一个事件类型,消息事件类型可以是string,int等常见类,也可以是自己自定义一个事件类,方便管理。这边演示,创建了一个EventMessage事件类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class EventMessage {

    private int type;
    private String message;

    public EventMessage(int type, String message) {
        this.type = type;
        this.message = message;
    }

    @Override
    public String toString() {

        return "type="+type+"--message= "+message;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

2.在需要订阅事件的模块中,注册eventbus。

MainActivity.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Override
    protected void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onStop() {
        super.onStop();

    }

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

关于EventBus的注册问题,说几点。

  1. 注册完了,在不用的时候千万别忘了unregister。
  2. 不能重复注册。注册之后,没有unregister,然后又注册了一次。
  3. register与unregister的时间根据实际需求来把控,官方的例子是在onStart()回调方法进行注册,onStop()回调方法进行unregister(),这边根据需求做了改动。

在需要接受事件的类中进行好register之后,需要在该类中创建一个方法来接收事件消息。

1
2
3
4
@Subscribe(threadMode = ThreadMode.MAIN)
    public void onReceiveMsg(EventMessage message) {
        Log.e(TAG, "onReceiveMsg: " + message.toString());
    }

创建的这个方法是有要求的。要求有如下几点。

  1. 该方法有且只有一个参数。
  2. 该方法必须是public修饰符修饰,不能用static关键字修饰,不能是抽象的(abstract)
  3. 该方法需要用@Subscribe注解进行修饰。

关于Subscribe注解的介绍,将在后面结合实例进行讲解。

3.在需要发送事件的地方,调用EventBus的post(Object event),postSticky(Object event)来通知订阅者。

SecondActivity.java

1
2
3
4
5
6
7
8
private View.OnClickListener mSendListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: " );
            EventMessage msg = new EventMessage(1,"Hello MainActivity");
            EventBus.getDefault().post(msg);
        }
    };

注意,该例子中,我使用了,EventBus.getDefault()方法,该方法会获取一个单例。所以才可以随时使用,如果不是用这种单例模式,需要想办法把订阅者(Subscriber)注册时用的EventBus的引用传给需要发送事件的模块中,简而言之就是Subscriber用的eventbus 和post方法需要的eventbus需要是同一个eventbus。

我们跑下代码测试一下。结果如下。

发现使我们想要的结果。那现在关于EventBus的基本使用,就明白了,主要就是三个部分,一个部分是Subscriber,需要在Subscriber类中进行register和unregister操作。一部分是在Subscriber中需要创建一个方法来接收事件信息,最后一部分就是在需要发送事件的环境使用post方法来发送事件信息。这三部分中所用到的eventBus实例得要是同一个实例。


@Subscribe 注解介绍

上面的接收事件的方法中,我们提到,必须要加入@Subscriber注解才可以,这其中的因果我们将在下篇文章进行分析,我们现在所要说的是Subscribe注解的用法。

@Subscribe是EventBus自定义的一种注解,他可接收三个参数。ThreadMode、boolean sticky、int priority。

所以上面的接收Event方法的代码,完整版的可以这样写:

1
2
3
4
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 1)
    public void onReceiveMsg(EventMessage message) {
        Log.e(TAG, "onReceiveMsg: " + message.toString());
    }

这三个参数可以根据需要选择是否使用。

threadMode 是用来决定onReceiveMsg将在哪种线程环境下被调用。EvenBus一共有5种Thread mode。

POSTING :这是EventBus的默认模式,表示post事件是什么线程,onReceiveMsg接收事件方法就在同样的线程环境中执行代码。例如:

订阅处

1
2
3
4
5
@Subscribe()
    public void onReceiveMsg(EventMessage message) {
        Log.e(TAG, "onReceiveMsg: " + message.toString());
        Log.e(TAG, "onReceiveMsg: current thread name ="+Thread.currentThread().getName() );
    }

发布处

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private View.OnClickListener mSendListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: " );
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String name = Thread.currentThread().getName();
                    Log.e(TAG, "run: thread  name = "+name );
                    EventMessage msg = new EventMessage(1,"Hello MainActivity");
                    EventBus.getDefault().post(msg);

                }
            }).start();
        }
    };

结果:

我们看到,他们所处的线程环境是一样的。

这种模式,适合使用在执行简单任务的情况下,不需要复杂运算,因为这种模式不需要做线程切换的判断逻辑,直接分发至相同的线程环境,速度快,耗时少。

MAIN:关于MAIN 这种线程模式,可以和MAIN_ORDERED一起讨论,他们都是表示,无论事件发布在什么线程,事件接收都是在主线程中执行。那MAIN模式和MAIN_ORDERED模式的区别在哪里呢?

区别在于,对于MAIN 模式,如果事件发布者post事件也是在主线程的话,会阻塞post事件所在的线程。意思就是连续post多个事件,如果接收事件方法执行完,才能post下一个事件。

post(1) ——> onReceiveMsg(1) ———>post(2)———>onReceiveMsg(2)———>post(3)————>onReceiveMsg(3)

如果事件发布者post事件不在主线程的话,连续post多个事件,同是在主线程是接收事件是耗时操作的话,执行的流程会是这样的。是非阻塞的(non-blocking)

post(1)——>post(2)——>psot(3)———>onReceiveMsg(3)

或者

post(1)——>post(2)——>psot(3)———>onReceiveMsg(2)——>onReceiveMsg(3)

那对于MAIN_ORDERED模式无论事件发布者post在什么线程环境,他的执行流程是都非阻塞的(non-blocking),和MAIN模式 下,post环境不是主线程的执行流程一样。

BACKGROUND:该模式下的时间发布者post线程环境与事件接收onReceiveMsg方法的线程环境关系如下:

post发布环境是主线程的话,事件接收处理的环境是一个子线程。

post发布环境是子线程的话,事件接收处理环境和post发布环境一样。

ASYNC:该模式表示,无论post环境是什么线程,事件接收处理环境都是子线程。

以上就是EventBus五种线程模式的解读。上面说了Subscriber注解的ThreadMode参数的含义。接着我们说另外两种参数的含义。

sticky

sticky是一个boolean型的参数,默认值是false,表示不启用sticky特性。那么sticky特性是什么呢?我们之前说的EventBus事件传递的例子的时候,我们都是先对订阅者(Subscriber)进行先注册的,然后再post事件的。那sticky的作用是在先post事件,后对订阅者注册这种开发场景的支持的。

举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 private View.OnClickListener mGoListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: post");
            EventMessage message = new EventMessage(233, "post message before");
            EventBus.getDefault().postSticky(message);
        }
    };

    private View.OnClickListener mRegisterListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: start register" );
            EventBus.getDefault().register(MainActivity.this);
        }
    };

我首先会点击触发go事件,通过postSticky()发送一个事件,然后再通过点击触发register事件,对Subscriber进行注册。结果log如下:

1
2
3
4
2018-12-08 10:40:54.444 9129-9129/com.example.yumryang.eventbusdemo E/MainActivity: onClick: post
2018-12-08 10:40:57.365 9129-9129/com.example.yumryang.eventbusdemo E/MainActivity: onClick: start register
2018-12-08 10:40:57.369 9129-9129/com.example.yumryang.eventbusdemo E/MainActivity: onReceiveMsg: type=233--message= post message before
2018-12-08 10:40:57.369 9129-9129/com.example.yumryang.eventbusdemo E/MainActivity: onReceiveMsg: current thread name =main

priority

该参数是int型,默认值是0,比较好理解,就像他的参数名所表示的那样,优先级。值越高,越先接收到事件,不过这里要注意一个问题,那就是优先级的比较前提是在post事件发布,onReceiveMsg事件接收处理这两方的线程环境相同的前提下,才有意义。同是与priority相配合使用的一个方法是cancelEventDelivery.关于它们的使用就不在演示,比较简单。

结尾

EventBus的入门篇,大致就说这些, 可能还有些注意问题,暂时没想起来,等下一篇,关于EventBus源码分析的时候到时候遇到了,再提出来吧。 关于EventBus的进阶版——> Android EventBus源码分析,基于最新3.1.1版本,看这一篇就够了!!