大家好,我是拭心,今天我们来了解一下有名的 Android 事件总线框架 EventBus
背后的原理!
首先来了解下它的特点和使用。
EventBus 3.0 简介
首先打开官方文档:http://greenrobot.org/eventbus/,一张嚣张的大图吸引了我的目光:
“Android 第一的事件库”,看起来很牛逼的样子啊,是不是真的这么牛呢?
首先看看介绍:
EventBus 是一个使用“观察者模式”的、松耦合的开源框架。它使用少量的几句代码就可以实现核心类之间的通讯,帮助我们简化代码、松依赖、加速开发。
观察者模式的确符合这个事件订阅、发布的场景,不了解这个模式的同学可以看看我之前写的两篇文章:
在 EventBus 之前,有什么事件通信的方法:
startActivityForResult()
发出请求 , onActivityResult()
接收回调
Activity 多层嵌套调用,多次 startActivityForResult,繁琐 = 易出错
嵌套 Fragement 调用,依赖外层 Activity 进行,还是繁琐
回调
子线程运行,生命周期不同步
线程间通信
EventBus 提出是为了解决什么问题呢:
EventBus 3.0 关键介绍
方便的注解
事件执行线程多种选择
事件的继承
不需要在 Application 或者其他部分配置,直接使用 EventBus.getDefault()
进行操作即可
可配置的
http://greenrobot.org/eventbus/documentation/
@Subscribe 注解
EventBus 3.0 之后使用 @Subscribe
注解来描述一个注册的方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
//注册方法所在线程
ThreadMode threadMode() default ThreadMode.POSTING; //默认与发生方法在同一线程
//是否为粘性事件
boolean sticky() default false;
//注册方法接收消息的优先级,当同一线程有多个注册方法时有效
int priority() default 0;
}
复制代码
四种 ThreadMode
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
log(event.message);
}
复制代码
EventBus 提供了四种 ThreadMode 值:
POSTING
MAIN
订阅方法执行在主线程
用于更新 UI
也要注意不执行耗时操作
BACKGROUND
订阅方法执行在单一的一个子线程
如果发送者不要主线程,那订阅方法就会执行在发送者线程
否则,使用一个单线程发送所有消息,所有消息串行执行
也要注意避免耗时操作,影响到在同一线程的其他订阅方法
ASYNC
粘性事件 Sticky Event
EventBus 还支持 粘性事件。
可以看到,粘性事件实现了对一些关键信息的缓存与更新,一般用于保存那些经常变化的信息,比如定位信息、传感器信息等等。
@Subscribe(sticky = true)
public void readStickyMsg(MessageEvent event){
//...
}
复制代码
优先级
我们可以设置一个注册方法的优先级:
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
...
}
复制代码
默认优先级是 0,同一 线程(ThreadMode) 中优先级高的订阅者会先于低的接收到消息。
注意,只有在同一线程中的订阅者优先级才有作用。
有优先级后 ,高优先级的订阅者就可以取消消息往后的传播,这也符合生活和一些场景的需求。
这通过调用 cancelEventDelivery(event)
方法:
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
...
// Prevent delivery to other subscribers
EventBus.getDefault().cancelEventDelivery(event) ;
}
Pasted from: http://greenrobot.org/eventbus/documentation/priorities-and-event-cancellation/
复制代码
注意,只有 ThreadMode 为 POSTING 的订阅方法可以拦截消息。
优先级设置后,界面上显示不明显,因为 EventBus 的消息发送效率很高,但是如果打断点的话就可以看到,的确是高优先级的方法先被调用。
配置
我们一般调用 EventBus 直接使用 EventBus.getDefault()
方法即可。如果你想要自己配置 EventBus,它也支持。
通过 EventBus.builder()
方法我们得到 EventBusBuilder
对象,然后配置 EventBus 的各种属性,比如这样:
EventBus eventBus = EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.build();
复制代码
EventBusBuilder
中可以配置的内容如下:
boolean logSubscriberExceptions = true;
boolean logNoSubscriberMessages = true;
boolean sendSubscriberExceptionEvent = true;
boolean sendNoSubscriberEvent = true;
boolean throwSubscriberException;
boolean eventInheritance = true;
boolean ignoreGeneratedIndex;
boolean strictMethodVerification;
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
List<Class<?>> skipMethodVerificationForClasses;
List<SubscriberInfoIndex> subscriberInfoIndexes;
复制代码
可以看到,默认 EventBus 中以下属性是设为 true 的:
logSubscriberExceptions: 记录订阅异常
logNoSubscriberMessages :记录没有目标订阅者的消息
sendSubscriberExceptionEvent :订阅方法异常时发送 SubscriberExceptionEvent
事件
sendNoSubscriberEvent :发送的消息没有订阅者时发送 NoSubscriberEvent
事件
eventInheritance :事件允许继承
AsyncExecutor
EventBus 还提供了一个异步线程池 AsyncExecutor
,使用它创建的线程,如果抛出异常,它会自动捕获,然后将异常信息包裹成一个 Event 发送出去。
AsyncExecutor 只是一个帮我们省去处理子线程抛出异常的工具类,不是 EventBus 的核心类。
我们可以在全局范围内调用 AsyncExector.create()
方法创建一个实例,然后调用 execute
方法执行异步任务,它的参数是 AsyncExecutor.RunnableEx
:
AsyncExecutor.create().execute(
new AsyncExecutor.RunnableEx() {
@Override
public void run() throws LoginException {
// No need to catch any Exception (here: LoginException)
remote.login();
EventBus.getDefault().postSticky(new LoggedInEvent());
}
}
);
Pasted from: http://greenrobot.org/eventbus/documentation/asyncexecutor/
复制代码
如果 RunnableEx
中抛出异常,AsyncExecutor
会捕获这个异常,然后包裹成 ThrowableFailureEvent
发送出去,注册了这个事件的方法将会得到调用。
订阅这个异常的例子如下:
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {
// do something
}
复制代码
EventBus 的使用
原文地址:http://blog.csdn.net/u011240877
下面我们演示一下 EventBus 的基本使用。
首先 gradle 引入依赖 EventBus 以及它的注解处理器:
根目录下的 gradle 的 dependencies 中添加 apt 依赖:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
复制代码
app 目录下的 gradle 中添加:
apply plugin: 'android-apt'
apt {
arguments {
eventBusIndex "top.shixinzhang.MyEventBusIndex"
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
复制代码
其中 eventBusIndex
你可以随意设置包名和文件名。
然后就可以发车了!
创建要订阅的事件:
public class MessageEvent implements Serializable {
private static final long serialVersionUID = -1371779234999786464L;
private String message;
private String time;
public MessageEvent(String message, String time) {
this.message = message;
this.time = time;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
复制代码
然后创建一个注册 EventBus 页面:
public class EventBusRegisterActivity extends AppCompatActivity {
@BindView(R.id.tv_event_info)
TextView mTvEventInfo;
@BindView(R.id.tv_event_info_priority)
TextView mTvEventInfoPriority;
@BindView(R.id.btn_cancel_delivery)
Button mBtnCancelDelivery;
private boolean mCancelDelivery;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_bus_test);
ButterKnife.bind(this);
}
/**
* 订阅方法,设置线程为 POSTING,优先级为 5
* @param event
*/
@Subscribe(threadMode = ThreadMode.POSTING, priority = 5)
public void readMessageFirst(MessageEvent event) {
mTvEventInfoPriority.setText("\n" + event.getMessage() + ", " + event.getTime());
if (mCancelDelivery) {
EventBus.getDefault().cancelEventDelivery(event);
}
}
@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
public void readMessage(MessageEvent event) {
mTvEventInfo.setText("\n" + event.getMessage() + ", " + event.getTime());
}
/**
* 为了演示效果,我们在按钮点击事件中注册事件
*/
@OnClick(R.id.btn_register_normal)
public void registerNormal() {
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
@OnClick(R.id.btn_unregister_normal)
public void unregisterNormal() {
EventBus.getDefault().unregister(this);
}
@OnClick(R.id.btn_cancel_delivery)
public void cancelDelivery() {
mCancelDelivery = !mCancelDelivery;
mBtnCancelDelivery.setText(mCancelDelivery ? "已拦截事件传递" : "点击拦截事件传递");
}
@OnClick(R.id.btn_jump_next)
public void jumpToNext() {
startActivity(new Intent(this, EventBusPosterActivity.class));
}
@OnClick(R.id.btn_jump_sticky)
public void jumpToSticky() {
startActivity(new Intent(this, EventBusStickyActivity.class));
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
复制代码
这个页面的功能如图所示:
先看下粘性事件订阅页面:
public class EventBusStickyActivity extends AppCompatActivity {
@BindView(R.id.tv_event_info)
TextView mTvEventInfo;
@BindView(R.id.btn_register_sticky_event)
Button mBtnRegisterStickyEvent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_bus_sticky);
ButterKnife.bind(this);
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void readStickyMsg(MessageEvent event){
mTvEventInfo.setText(mTvEventInfo.getText() + "\n" + event.getMessage() + ", " + event.getTime());
}
@OnClick(R.id.btn_jump_next)
public void jumpToNext(){
startActivity(new Intent(this, EventBusPosterActivity.class));
}
@OnClick(R.id.btn_register_sticky_event)
public void registerSticky(){
EventBus.getDefault().register(this);
}
@OnClick(R.id.btn_unregister_sticky_event)
public void unregisterSticky(){
EventBus.getDefault().removeStickyEvent(MessageEvent.class);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
复制代码
功能如图所示:
一个显示订阅方法接收信息的文字
一个点击后跳转到发送事件页面的按钮
两个用于注册和解除注册粘性事件的按钮
好,接着再看一下发送事件页面:
public class EventBusPosterActivity extends AppCompatActivity {
@BindView(R.id.tv_event_info)
TextView mTvEventInfo;
@BindView(R.id.btn_post_normal)
Button mBtnPostNormal;
@BindView(R.id.btn_post_sticky_event)
Button mBtnPostStickyEvent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_bus_post);
ButterKnife.bind(this);
}
@OnClick(R.id.btn_post_normal)
public void postNormalEvent(){
EventBus.getDefault().post(new MessageEvent("另外界面的主线程,普通消息", DateUtils.getCurrentTime()));
finish();
}
@OnClick(R.id.btn_post_sticky_event)
public void postStickyEvent(){
EventBus.getDefault().postSticky(new MessageEvent("另外界面的主线程,粘性消息", DateUtils.getCurrentTime()));
finish();
}
}
复制代码
这个页面很简单,两个发送普通事件和粘性事件的按钮。
运行效果
演示下普通事件的注册、解除注册、以及高优先级拦截事件的运行效果:
从上面的动图可以看到:
注册事件监听后,订阅的方法就可在 EventBus 发送事件后收到调用
优先级高的会比低的先收到调用,界面上显示不明显,但是打断点就可以看到先后的调用顺序
优先级高的拦截事件后,低优先级的就不会收到新的事件
解除注册后,也不会收到新的事件
接着看一下 粘性事件的注册、解除注册的效果:
从上面的动图可以看到:
可以看到,粘性事件的确适用于获取一些需要缓存的关键信息,比如城市、天气等等。
总结
这篇文章介绍了 EventBus 3.0 的主要特点和使用,可以发现,它的确很容易使用,目前能想到的事件通讯基本都可以满足,代码耦合也不严重。
而 EventBus 的缺点就是,会导致业务逻辑比较分散,不直观。尤其是在运行时触发多种事件、多个订阅方法时。不过这应该是解耦的双刃剑吧。
下一篇文章我们分析下 EventBus 的核心功能是如何实现的。
有些之前不了解的内容,在写了 Sample 之后才发现错在哪儿,知行合一,知行合一啊!
评论