转载请标明出处:【Gypsophila 的博客】 http://blog.csdn.net/astro_gypsophila/article/details/69939299
EventBus 基本使用和进阶配置
EventBus 源码试读(一)
EventBus 源码试读(二)
EventBus 源码试读(三)
文章内容基于 EventBus 3,版本 88f3149。
EventBus 是开源的 Android 事件订阅/发布框架,为的是松散耦合,加快程序开发。
首先介绍几个概念用于接下来的文章:
1. 事件(Event):被发送的一个载体,可以称为消息。
2. 订阅者(Subscriber):订阅了某类事件类型的对象。在发布者发布了这类事件类型,订阅者内用 @Subsribe 注解且满足条件的方法将被 EventBus 执行,称为事件响应函数
。
3. 发布者(Publisher):发布某类型事件的对象,EventBus 中通过 post 发布事件。
以下核心内容主要来自 EventBus 官方文档,可建议直接去阅读英文文档,进行最正确的理解。代码示例仅为个人演示和比较。演示界面为 ActivityFirst–>ActivitySecond–>ActivityThird
1.Events(以下称为事件)就是没有特定要求的 POJO,我们定义一个事件,来进行我们的订阅、发布。
public class MessageEvent {
// 如果不需要携带其他信息的话,甚至这边的 message 都可以省去。
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
2.在你所需要订阅该 MessageEvent 事件的订阅者类中实现当收到事件后的处理方法,以 @Subscribe 注解定义为 事件响应函数
。这样就会当事件被发布后,订阅者中的事件响应函数就会被调用起。
形如:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(this, "ActivityFirst 收到:" + event.message, Toast.LENGTH_SHORT).show();
}
注意:在 EventBus 3 中订阅事件的处理方法名字可以随意取,由 @Subscribe 注解来标明它是响应函数;在 EventBus 2 中则是方法名有命名约定的。
订阅者中处理事件还需要去注册自己,告诉 EventBus 有该类型事件发布时要通知我。另外还有移除注册,这是不是很像广播的注册和移除呢?根据需要,在适宜生命周期来做这事,若使用过程中事件响应不起作用,检查下是否在不合适的时机进行了 unregister(this)。
直接将代码贴上:
public class ActivityFirst extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
// 注册
EventBus.getDefault().register(this);
Button btnFirst = (Button) findViewById(R.id.btnFirst);
btnFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ActivityFirst.this, ActivitySecond.class));
}
});
}
@Override
protected void onDestroy() {
// 解除注册
EventBus.getDefault().unregister(this);
super.onDestroy();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(this, "ActivityFirst 收到:" + event.message, Toast.LENGTH_SHORT).show();
}
}
3.最后一步就是在你代码的任何一处,去发布事件。当前已经注册的所有订阅者且匹配一事件类型,都将收到。
public class ActivitySecond extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button btnPost = (Button) findViewById(R.id.btnPost);
btnPost.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发布事件
EventBus.getDefault().post(new MessageEvent("来自 ActivitySecond 的事件"));
}
});
Button btnSecond = (Button) findViewById(R.id.btnSecond);
btnSecond.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ActivitySecond.this, ActivityThird.class));
}
});
}
}
也许例子举得不够生动,但是却是因为由于例子太简单而体现不出它简洁地解耦的特点。
ThreadMode 可以理解为响应函数所在的线程
。
刚刚我们的 @Subscribe 注解中写明的 threadMode = ThreadMode.MAIN 又是什么意思呢。
这表示的是希望该方法在接受到事件后,是在主线程中运行的。既然有 MAIN,我们再详细看下总共有哪些可选。
在 EventBus 中,你可以定义以下四种 ThreadMode 来标明事件处理函数被调用的线程。
* POSTING:
表示订阅者中响应函数被调用所处的线程是与发布事件时的线程为同一个线程。这意味着它们表现得就像 post 后几乎很快就响应到事件,因为避免了切换线程的开销,最小的代价。但如果 post 时位于在主线程,这时响应事件处理不能有耗时操作。
* MAIN:
表示明确订阅者中响应事件函数将被主线程中调用起,如果当前 post 时位于主线程了,那么就是同步调用起响应函数,这时也没有切换线程的开销;反之处于子线程,则产生了切换回主线程的开销。MAIN 这个模式要明确记住不能处理耗时操作,阻塞主线程。
* BACKGROUND:
表示订阅者中响应函数将在一个后台线程被调用起。如果 post 时线程不是主线程,那么事件响应函数将是直接在该线程调用。如果是主线程,那么 EventBus 将会使用唯一一个后台线程进行分发事件的响应,所以这个模式应该尽快处理完响应函数内容,避免在后台线程阻塞而影响其他在排队的事件,因为唯一的后台线程有序处理着所有这个模式分发来的事件。
* ASYNC:
表示订阅者中的响应函数将在一个独立的线程被调用起,由 EventBus 所维护的线程池复用空闲线程来处理,因此需要进行耗时操作的事件响应可以使用这模式。
到现在,我们知道事件就是一个 POJO 的表示。但对于 EventBus 内来说,还可以特殊声明一个事件是否为 sticky 事件(不知道怎么翻译合适,就这样写吧)。
那么一般事件和 sticky 事件区别是什么呢?
sticky 事件在事件发布后,如果后来的订阅者再去订阅该类型事件,仍然可以收到该类型最近的一个 sticky 类型,因为被存储在内存之中。
接下来用代码做对比试验:
我们将 ActivitySecond.java 中多加一个:
Button btnPostSticky = (Button) findViewById(R.id.btnPostSticky);
btnPostSticky.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().postSticky(new MessageEvent("来自 ActivitySecond 的 sticky 事件"));
}
});
ActivityThird.java 如下:
public class ActivityThird extends AppCompatActivity {
private TextView tvThird;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
EventBus.getDefault().register(this);
tvThird = (TextView) findViewById(R.id.tvThird);
tvThird.setText("third activity");
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onStickyEvent(MessageEvent event) {
Toast.makeText(this, "ActivityThird 收到:" + event.message, Toast.LENGTH_SHORT).show();
}
}
由 Toast 的触发,相信应该可以理解了。一般事件发布之后,当前所有已注册该事件类型的订阅者都将收到,这个很容易理解,EventBus 遍历那些已经注册过的订阅者,然后调用订阅者的响应函数。而 sticky 事件在发布之后,仍然允许后来注册的订阅者响应函数去响应对应类型的 sticky 事件。ActivityThird 一进入就触发了 Toast,就是 onCreate 中注册了,然后由 EventBus 对这个订阅者发布这一类型的 sticky 事件。记住,是这一类型的 sticky 事件的最新一个事件,而不是所有 sticky 事件的最新一个。
另外我们可以显式调用 EventBus 的 api 去获得或者移除一个类型的 sticky 事件。
获得与移除
MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// "Consume" the sticky event
EventBus.getDefault().removeStickyEvent(stickyEvent);
// Now do something with it
}
MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// Now do something with it
}
之前我们已经看过在 @Subscribe() 中有 sticky 和 threadMode,现在让我们看下 @Subscribe 里面还有什么。
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false;
/** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int priority() default 0;
}
以上代码很清楚的知道,关于响应函数如果注解中 threadMode 默认为 ThreadMode.POSTING;sticky 默认为false 即是一个普通事件而不是 sticky 事件;默认的优先级为 0,优先级高的订阅者将比优先级低的先接收到事件。值得注意的是,条件是在同一个线程中(threadMode 一致),在不同的 threadMode 值的订阅者之间的接受顺序并不会有影响。接下来以示例说明。
在 ActivityFirst.java 中修改代码如下:
// @Subscribe(threadMode = ThreadMode.MAIN)
// public void onMessageEvent(MessageEvent event) {
// Toast.makeText(this, "ActivityFirst 收到:" + event.message, Toast.LENGTH_SHORT).show();
// }
@Subscribe(priority = 1, threadMode = ThreadMode.POSTING)
public void onMessageEvent1(MessageEvent event) {
Log.e("gypsophila","onMessageEvent1");
}
@Subscribe(priority = 2, threadMode = ThreadMode.POSTING)
public void onMessageEvent2(MessageEvent event) {
Log.e("gypsophila","onMessageEvent2");
// EventBus.getDefault().cancelEventDelivery(event);
}
@Subscribe(priority = 0, threadMode = ThreadMode.POSTING)
public void onMessageEvent3(MessageEvent event) {
Log.e("gypsophila","onMessageEvent3");
}
打印日志如下:
com.gypsophila.eventbustest E/gypsophila: onMessageEvent2
com.gypsophila.eventbustest E/gypsophila: onMessageEvent1
com.gypsophila.eventbustest E/gypsophila: onMessageEvent3
EventBus 中提供 cancelEventDelivery(Object event) 来取消事件的进一步分发,则接下来的订阅者将不会收到事件。一般的情景都是用于发送事件,然后在优先级高的订阅者中处理完自己逻辑,之后不然让事件继续分发给其他的订阅者,则取消事件。需要注意的是取消操作,被限制在只能使用 threadMode 为 ThreadMode.POSTING 中。
@Subscribe(priority = 1, threadMode = ThreadMode.POSTING)
public void onMessageEvent1(MessageEvent event) {
Log.e("gypsophila","onMessageEvent1");
}
@Subscribe(priority = 2, threadMode = ThreadMode.POSTING)
public void onMessageEvent2(MessageEvent event) {
Log.e("gypsophila","onMessageEvent2");
// 在优先级高的订阅者响应函数中取消
EventBus.getDefault().cancelEventDelivery(event);
}
@Subscribe(priority = 0, threadMode = ThreadMode.POSTING)
public void onMessageEvent3(MessageEvent event) {
Log.e("gypsophila","onMessageEvent3");
}
日志打印如下:
com.gypsophila.eventbustest E/gypsophila: onMessageEvent2
只打印了 onMessageEvent2 响应的日志,其他的都因为被取消继续分发。
添加以下内容,确保 EventBus 正常工作。
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
(java.lang.Throwable);
}
使用 EventBusBuilder 类来配置 EventBus 的方方面面。
进入 EventBus 类中,可以看到 EventBusBuilder 会给 EventBus 赋值几个默认为 true 的属性配置值,下面来看下:
1. logSubscriberExceptions: 打印订阅异常日志
2. logNoSubscriberMessages :打印没有目标订阅者的消息
3. sendSubscriberExceptionEvent :订阅方法异常时发送 SubscriberExceptionEvent 事件
4. sendNoSubscriberEvent :发送的消息没有订阅者时发送 NoSubscriberEvent 事件
5. eventInheritance :事件允许继承
1-4 点属性如果配置是 true 的,结果是会根据它们的值判断是否要打印出相关的信息,相信可以这么简单理解吧。
所以在官网的示例中,以下的 EventBus 配置会使得 keep quite,就是不让它打印日志了。
EventBus eventBus = EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.build();
另外,全局 app 中我们一般是使用 EventBus.getDefault()
这样的单例,那么如何将我们想要更改后的配置应用于单例中,从而仍然使用 EventBus.getDefault()
这样的简便写法?
EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.installDefaultEventBus();
这样就可以不是到处新建一个 EventBus 实例,同样是 EventBus.getDefault()
拿到新配置的 EventBus。
具体的配置可以查看 http://greenrobot.org/files/eventbus/javadoc/3.0/org/greenrobot/eventbus/EventBusBuilder.html
Subscriber index 是 EventBus 3 的新特性,是用于加速初始化订阅者注册的一个优化点。
Subscriber index 在编译期间通过 EventBus annotation processor(注解处理器) 被创建起来的。这个特性并不是必须使用的,但是是被推荐的,因为通过这个可以更快速度获得订阅者,从而去触发响应事件。(如果还不明白具体的概念和作用,可以看接下来的 EventBus 的源码分析文章)
注意,只有被 @Subscribe 注解的方法,且订阅者类和事件类都必须为 public 才可以被建立索引。同时,由于 java 注解技术限制,@Subscribe 在匿名内部类中是不会被识别的。
当 EventBus 不能使用索引时,它将自动回退到在运行时进行反射,因此 EventBus 仍然能够工作,只是稍微慢一点而已的。
以上差不多说明了原因,推荐你去使用 Subscriber Index 在编译期间建立了订阅者索引,所以更快获得订阅者,以调用它的响应函数;而如果不设置 Subscriber Index 其实也能使 EventBus 工作的很好,虽然获得订阅者时通过反射是慢了一点点而已,但仍然会帮助将订阅者缓存起来。
如果你不是使用 Android Gradle Plugin 版本 2.2.0 或者更高版本,那使用 android-apt 配置。
为了能生成索引,你需要添加 EventBus 注解处理器到你工程的 build.gradle 下,使用 annotationProcessor 属性。然后 eventBusIndex 参数去指定要生成的索引类的全限定名。具体配置如下(来自官方文档):
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
添加完以上配置,然后再 rebuild 你的工程,之后搜索 MyEventBusIndex 这个索引类,你应该会看到内部已经预先添加一个 HashMap 存放编译期间我们工程中的订阅者类中的响应函数,所以查找订阅者速度也就快起来了。
如果上面的方式对你无效,你可以添加 EventBus 注解处理器,使用 android-apt Gradle plugin,具体配置如下(来自官方文档):
我没有验证过,相信官方的应该没有问题。
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.example.myapp.MyEventBusIndex"
}
}
以上两种方式,任意一种生效,都将为你生成 eventBusIndex 所指定的类。然后我们就按接下来的配置使 EventBus 真正生效。
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
还记得如何去配置默认单例的 EventBus 吗?
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
关于这部分,根据相同原理应用到依赖库的一部分(而非最终应用程序),你将拥有多个索引类,在 EventBus 设置中添加,举例:
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
关于这部分内容,我确实不是很明白什么意思。请了解的同学留言告知一下,谢谢。
AsyncExecutor 像线程池一样,不过用来处理错误(异常)的。异常被抛出时,AsyncExecutor 自动包裹异常入一个事件之中,事件将被自动发布。
更加具体的内容还是推荐直接看官方文档。
关于 EventBus 基本使用和进阶配置就到这里,若文章有不准确的地方还请指出,谢谢。