发布/订阅事件总线。它可以让我们很轻松的实现在Android各个组件之间传递消息,并且代码的可读性更好,耦合度更低。
- 简化组件之间的通讯
- 事件的发送着与接受者完全解耦
- 完美解决UI(如:Activities、Fragments)和后台线程之间切换
- 避免复杂且容易出错的依赖关系和生命周期问题
1.开始EventBus之旅
在使用EventBus之前,需要添加依赖:模块的build.gradle文件中
dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
}
1、步骤一:定义事件
事件没有任何特定的要求,一个普通的java对象
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
2、步骤二:准备订阅者
订阅者实现事件处理方法(也称为 订阅方法),这些方法在事件发布时调用。它们要用@Subscribe注解定义,并指定线程模型。
注意:EventBus3.0方法名可以随意起,不像EventBus2.0版本有命名约定。
// 当MessageEvent事件发布时,该方法在UI线程调用(因为线程模型指定为UI线程)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.d(TAG, event.message + " ### receive thread name: " + Thread.currentThread().getName());
}
// 该方法在发布MessageEvent事件的线程调用(没有指定线程模型,会在发布事件线程执行)
@Subscribe
public void handleSomethingElse(MessageEvent event) {
}
订阅者也需要注册或者注销到总线,只有订阅者被注册才能收到消息。在Android中,通常根据Activity或者Fragment的生命周期进行注册,例如:Activity中的onCreate/onDestroy
// Activity 中
@Override
protected void onCreate(Bundle savedInstanceState) {
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this);
super.onDestroy();
}
3、发布事件
在代码的任何地方都可以发布事件。所有注册订阅者将接收匹配事件类型
EventBus.getDefault().post(new MessageEvent("post thread name: " + Thread.currentThread().getName());
2.线程模型(ThreadMode)
EventBus提供了线程模型,可以帮助我们处理线程:订阅方法可以在不同于发布事件的线程中处理。常见用例涉及到UI更新,在Android中,UI更新必须在UI(main)线程执行。另一方面,网络或者任何耗时的任务不能运行在主线程中。EventBus帮助我们处理这个任务并与UI线程同步(无需深入研究线程切换,或使用AsyncTask等)。
在EventBus中,可以使用四个线程模型之一定义调用事件处理函数的线程。
ThreadMode: POSTING (默认)
订阅事件函数指定了线程模型为POSTING:该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和事件处理函数在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
// ThreadMode 是可选的,默认值为ThreadMode.POSTING
// 与发布事件在同一个线程调用
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessageEventPosting(MessageEvent event) {
Log.d(TAG, "posting: " + Thread.currentThread().getName());
}
ThreadMode: MAIN
订阅事件函数指定了线程模型为MAIN:不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。
// 在Android UI 主线程调用
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEventMain(MessageEvent event) {
Log.d(TAG, "main: " + Thread.currentThread().getName());
}
ThreadMode: MAIN_ORDERED
订阅事件函数指定了线程模型为MAIN_ORDERED:不论事件在哪个线程中发布出去,该事件处理函数都会在UI线程中执行。与MAIN模型区别在于:事件处理函数总是在主线程排队执行
// 在Android UI 主线程调用
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessageEventMainOrdered(MessageEvent event) {
Log.d(TAG, "main_ordered: " + Thread.currentThread().getName());
}
ThreadMode: BACKGROUND
订阅事件函数指定了线程模型为BACKGROUND:如果事件是在UI线程中发布出来的,那么该事件处理函数就会在单线程串行执行,尽量不要堵塞线程;如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
// 在后台线程调用
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEventBackground(MessageEvent event) {
Log.d(TAG, "background: " + Thread.currentThread().getName());
}
ThreadMode: ASYNC
订阅事件函数指定了线程模型为ASYNC:无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。发布事件不需要等待,适合耗时的事件处理函数,但避免同时触发大量长时间的事件处理函数。 同样,此事件处理函数中禁止进行UI更新操作。
// 在单独的线程调用
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessageEventAsync(MessageEvent event) {
Log.d(TAG, "async: " + Thread.currentThread().getName());
}
为了验证这些线程模型,写了一个简单的例子:
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessageEventPosting(MessageEvent event) {
Log.d(TAG, "posting: " + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEventMain(MessageEvent event) {
Log.d(TAG, "main: " + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEventBackground(MessageEvent event) {
Log.d(TAG, "background: " + Thread.currentThread().getName());
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessageEventAsync(MessageEvent event) {
Log.d(TAG, "async: " + Thread.currentThread().getName());
}
分别使用上面四个方法订阅同一事件,打印他们运行所在的线程。首先我们在UI线程中发布一条MessageEvent的消息,看下日志打印结果是什么。
findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "postEvent: " + Thread.currentThread().getName());
EventBus.getDefault().post(new MessageEvent());
}
});
打印结果如下:
8614-8614/com.example.eventbus.demo D/event_bus: postEvent: main
8614-8664/com.example.eventbus.demo D/event_bus: ASYNC: pool-1-thread-1
8614-8614/com.example.eventbus.demo D/event_bus: MAIN: main
8614-8614/com.example.eventbus.demo D/event_bus: POSTING: main
8614-8665/com.example.eventbus.demo D/event_bus: BACKGROUND: pool-1-thread-2
从日志打印结果可以看出,如果在UI线程中发布事件,则线程模型为POSTING的事件处理函数也执行在UI线程,与发布事件的线程一致。线程模型为ASYNC的事件处理函数执行在名字叫做pool-1-thread-1的新的线程中。而MAIN的事件处理函数执行在UI线程,BACKGROUND的时间处理函数执行在名字叫做pool-1-thread-2的新的线程中。
我们再看看在子线程中发布一条MessageEvent的消息时,会有什么样的结果。
findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "postEvent: " + Thread.currentThread().getName());
EventBus.getDefault().post(new MessageEvent());
}
}).start();
}
});
打印结果如下:
8754-8807/com.example.eventbus.demo D/event_bus: postEvent: Thread-2
8754-8807/com.example.eventbus.demo D/event_bus: BACKGROUND: Thread-2
8754-8807/com.example.eventbus.demo D/event_bus: POSTING: Thread-2
8754-8808/com.example.eventbus.demo D/event_bus: ASYNC: pool-1-thread-1
8754-8754/com.example.eventbus.demo D/event_bus: MAIN: main
从日志打印结果可以看出,如果在子线程中发布事件,则线程模型为POSTING的事件处理函数也执行在子线程,与发布事件的线程一致(都是Thread-2)。BACKGROUND事件模型也与发布事件在同一线程执行。ASYNC则在一个名叫pool-1-thread-1的新线程中执行。MAIN还是在UI线程中执行。
3.配置
EventBus提供一个EventBusBuilder配置类。例如:如何构建没有log信息输出的EventBus,例如没有订阅者没有注册。
EventBus eventBus = EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.build();
另一个例子,当事件处理函数执行抛出异常
EventBus eventBus = EventBus.builder().throwSubscriberException(true).installDefaultEventBus();
注意:默认情况,EventBus会捕获订阅函数抛出的异常,并发送SubscriberExceptionEvent事件,该事件不是必须处理的。
配置默认的EventBus实例对象
我们可以在代码的任何地方通过EventBus.getDefault()获取共享的EventBus实例对象。EventBusBuilder也可以使用installDefaultEventBus()函数配置默认的EventBus实例对象。
EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
4.黏性事件 Sticky Events
除了上面讲的普通事件外,EventBus还支持发送黏性事件。何为黏性事件呢?简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。具体用法如下:
黏性事件例子
写一个简单的黏性事件例子说明:
int index = 0;
findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发布黏性事件
EventBus.getDefault().postSticky(new MessageEvent("event: " + index++));
}
});
findViewById(R.id.register).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 注册
EventBus.getDefault().register(MainActivity.this);
}
});
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEventMain(MessageEvent event) {
Log.d(TAG, "MAIN: " + event.message);
}
代码很简单,有两个点击按钮,一个用来发布黏性事件(没发送一次,index+1),一个用来注册。在没有点击注册按钮之前发送黏性事件,然后点击注册按钮,会看到打印的log日志如下:
11260-11260/com.example.eventbus.demo D/event_bus: MAIN: event: 0
这就是粘性事件,能够收到注册之前发送的消息。但是它只能收到最新的一次消息,比如说在未注册之前已经发送了多条黏性消息了,然后再注册只能收到最近的一条消息。这个我们可以验证一下,我们连续点击5次发送黏性事件按钮(index自增到4),然后再点击注册按钮,打印结果如下:
11166-11166/com.example.eventbus.demo D/event_bus: MAIN: event: 4
由打印结果可以看出,发送了5次黏性事件,但只收到最近的一条黏性事件。
获取和删除黏性事件
最后一个黏性事件会在注册时自动传递给匹配的订阅者,但有时需要手动检查黏性事件更方便,另外还有可能需要删除黏性事件,不需要在注册时传递给匹配的订阅者。
MessageEvent event = EventBus.getDefault().getStickyEvent(MessageEvent.class);
if (event != null) {
EventBus.getDefault().removeStickyEvent(event);
}
removeStickyEvent有重载函数,当传入的是Class时,返回保存的黏性事件。
MessageEvent event = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
if (event != null) {
// Now do something with it
}
5.订阅索引 Subscriber Index
订阅索引在EventBus3.0是一个新的特性功能。可选的,可以加快订阅的注册速度。
订阅索引通过APT技术在编译器生成的,在Android中,推荐使用。
订阅索引前提条件:被@Subscriber注解标记且是public修饰。由于APT技术自身的原因,匿名中@Subscriber注解不能被识别。
当EventBus不能够使用索引,会自动回退使用反射技术实现,依然能够工作,仅仅影响性能。
怎么生成索引呢?
如果你不是使用Android Gradle Plugin 版本是 2.2.0 或者更高,使用android-apt配置。
为了启动索引生成,需要使用annotationProcessor属性将EventBus注解处理器添加到构建中,此外还需要设置eventBusIndex参数,以指定要生成的索引的完全限定类。
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
使用kapt
如果你在kotlin代码中使用EventBus,需要kapt替代annotationProcessor
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
kapt {
arguments {
arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
}
}
使用android-apt
如果上面的内容不能工作,您可以使用Android-APT Gradle插件将EventBus注释处理器添加到您的构建中
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.1.1'
apt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
apt {
arguments {
eventBusIndex "com.example.myapp.MyEventBusIndex"
}
}
怎么使用索引呢?
成功构建项目后,将生成使用eventBusIndex指定的类。
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();
Libraries中使用索引
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
6.混淆 ProGuard
-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);
}
7.AsyncExecutor
AsyncExecutor与线程池类似,但处理失败(异常)。失败将引发异常,AsyncExecutor将这些异常包装在一个事件中,该事件将自动发布.
通常,可以调用AsyncExecutor.create()来创建实例,并将其保存在应用程序范围内。然后,实现RunnableEx接口并将其传递给AsyncExecutor的Execute方法。
与Runnable不同,RunnableEx可能引发异常。如果RunnableEx实现抛出异常,它将被捕获并包装到ThrowableFailureEvent中,该事件将被发布。
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());
}
}
);
接收事件
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleLoginEvent(LoggedInEvent event) {
// do something
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {
// do something
}
AsyncExecutor Builder
如果要自定义AsyncExecutor实例,请调用静态方法AsyncExecutor.builder()。它将返回一个生成器,用于自定义EventBus实例、线程池和失败事件的类。
另一个自定义选项是执行范围,它给出故障事件上下文信息。例如,故障事件可能仅与特定活动实例或类别相关。
如果自定义故障事件类实现HasExecutionScope接口,AsyncExecutor将自动设置执行范围。与此类似,您的用户可以查询其执行范围的失败事件,并根据它做出反应。