EventBus 使用(全面分析,细节提醒)
EventBus使用
概述
关于 EventBus 在开发中经常会选择使用它来进行模块间通信、解耦。平常使用这个库只是很浅显的操作三部曲,register,post,unregister。来达到开发目的。始终有种不明确,模糊的操作感。因此准备对EventBus进行一个深入,全面的理解,消除模糊,片面感,让以后在使用这个库的时候,有更好的掌握和使用。并记录下来,方便以后查阅。关于EventBus会分两章进行记录,本篇文章,是对EventBus的使用做一个全面的介绍,另一篇文章则会对EventBus库的源码进行分析,看看他的实现原理是什么样的。
关于
EventBus 是一个开源库,它利用发布/订阅者者模式来对项目进行解耦。它可以利用很少的代码,来实现多组件间通信。android的组件间通信,我们不由得会想到handler消息机制和广播机制,通过它们也可以进行通信,但是使用它们进行通信,代码量多,组件间容易产生耦合引用。关于EventBus的工作模式,这里引用一张官方图帮助理解。
上面这张图还是很好理解的,Publisher(发布者)通过post()方法,把Event事件发布出去,Subscriber(订阅者)在onEvent()方法中接收事件,可能现在你对Publisher,Subscriber, onEvent()这几个词还是不理解,没关系,别着急,放下看慢慢的你就懂了。
为什么会选择使用EventBus来做通信?
- 简化了组件间交流的方式
- 对事件通信双方进行解耦
- 可以灵活方便的指定工作线程,通过ThreadMode
- 速度快,性能好
- 库比较小,不占内存
- 使用这个库的app多,有权威性
- 功能多,使用方便
现在,我们就来体验EventBus的强大之处吧。
使用
导入项目
在build.gradle文件中导入EventBus库。
implementation 'org.greenrobot:eventbus:3.1.1'
在项目的混淆文件中,加入EventBus 的混淆规则,这个千万别忘了,不然会出现,debug版本测试OK,release版本subscriber 收不到消息等诡异问题。
-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方法。
- 创建一个事件类型,消息事件类型可以是string,int等常见类,也可以是自己自定义一个事件类,方便管理。这边演示,创建了一个EventMessage事件类。
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;
}
}
- 在需要订阅事件的模块中,注册eventbus。
MainActivity.java
@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的注册问题,说几点。
- 注册完了,在不用的时候千万别忘了unregister。
- 不能重复注册。注册之后,没有unregister,然后又注册了一次。
- register与unregister的时间根据实际需求来把控,官方的例子是在onStart()回调方法进行注册,onStop()回调方法进行unregister(),这边根据需求做了改动。
在需要接受事件的类中进行好register之后,需要在该类中创建一个方法来接收事件消息。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onReceiveMsg(EventMessage message) {
Log.e(TAG, "onReceiveMsg: " + message.toString());
}
创建的这个方法是有要求的。要求有如下几点。
- 该方法有且只有一个参数。
- 该方法必须是public修饰符修饰,不能用static关键字修饰,不能是抽象的(abstract)
- 该方法需要用@Subscribe注解进行修饰。
关于Subscribe注解的介绍,将在后面结合实例进行讲解。
3. 在需要发送事件的地方,调用EventBus的post(Object event),postSticky(Object event)来通知订阅者。
SecondActivity.java
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方法的代码,完整版的可以这样写:
@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接收事件方法就在同样的线程环境中执行代码。例如:
订阅处
@Subscribe()
public void onReceiveMsg(EventMessage message) {
Log.e(TAG, "onReceiveMsg: " + message.toString());
Log.e(TAG, "onReceiveMsg: current thread name ="+Thread.currentThread().getName() );
}
发布处
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事件,后对订阅者注册这种开发场景的支持的。
举个例子:
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如下:
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源码分析,看这一篇就够了!