本文中部分内容来自对官方文档的翻译,并且基于EventBus 3.2.0的版本,地址如下:https://github.com/greenrobot/EventBus
EventBus的作用:应用到安卓开发中可以帮助我们简化Activity,Fragment,Thread以及不同Service之间的通信(数据传递)过程。
具有如下优点:
现在我们知道EventBus的作用和优点,接下来介绍一下具体应该如何使用,以及有哪些特性:
1. 添加依赖:
implementation 'org.greenrobot:eventbus:3.2.0'
2. 基本使用
EventBus的使用基本可以分为以下几个步骤:
(1)定义传递的事件类MessageEvent:
传递的事件对象就是一个数据的载体,完全的普通对象(POJO,plain old Java object)。例如:
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
(2)准备事件订阅者
在订阅者方法中,我们需要实现接收特定事件后的处理逻辑。事件订阅者方法需要使用@Subscribe注解修饰。例如:
// This method will be called when a MessageEvent is posted (in the UI thread for Toast)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
doSomethingWith(event);
}
注解中的ThreadMode指定了事件处理逻辑执行的线程。例如,threadMode = ThreadMode.MAIN 表示onMessageEvent中的代码将在主线程中执行。
在Activity中订阅事件时,首先需要注册EventBus,否则我们是接收不到事件的。
如果想在Activity切到后台无法与用户交互时停止订阅某个事件,可以在onStop方法中取消注册。例如:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
EventBus.getDefault()获取的是全局唯一的EventBus单例。
(3)发送事件
接下来就可以在任何地方发送事件,只要是符合发送事件类型的订阅者(subscriber)或者说是接收器(receiver),都将会接收到该事件并执行对应的事件处理逻辑。例如:
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
以上就是最简单的使用EventBus的步骤。下面具体介绍一下ThreadMode。
3. ThreadMode
EventBus能够帮助你处理线程切换的细节。比如在安卓中,UI的更新都必须在主线程完成,与之相对的,还有诸如网络请求,长耗时的任务等都应该避免在主线程执行。EventBus在发布订阅事件传递消息的同时,能够帮我们灵活的处理上述场景,而这正是ThreadMode的作用。
(1)ThreadMode.POSTING
这种模式下,订阅者中的代码将会在发布事件的线程中执行。由于完全避免了线程间切换的开销,对于简单任务(能够在短时间内完成)而言,这是推荐的模式,也是默认模式。当然了,为了避免阻塞发布事件的线程(可能是主线程),订阅者中的代码应当避免长耗时的操作。例子如下:
// Called in the same thread (default)
// ThreadMode is optional here
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
log(event.message);
}
(2)ThreadMode.MAIN
这种模式下,订阅者中的代码将会在主线程(即UI线程)中执行。如果事件是在主线程被发送,那么订阅者中的代码将会立即执行。由于代码是在主线程执行,毫无疑问,同样需要避免在订阅者中执行长耗时的操作。例子如下:
// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
(3)ThreadMode.BACKGROUND
这种模式下,订阅者中的代码会在非主线程的后台线程中执行。根据事件发布所在的线程,可以分为两种情况:
这种模式虽然确保了订阅者的代码不会阻塞主线程,但是为了避免阻塞其他发布线程,也要尽量避免耗时操作。例子如下:
// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
(4)ThreadMode.ASYNC
这种模式下,订阅者中的代码既不会在主线程也不会在发布事件的线程中执行,而是在EventBus维护的线程池中的线程上执行。如果采用这种模式,事件的发布和事件订阅代码的执行,二者间是完全异步的关系。所以如果有长耗时的操作或者是网络请求等,建议采用这种模式。例子如下:
// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
实际上,EventBus对象是可配置的,我们可以通过EventBusBuilder来对其进行配置,通过使用官方提供的部分api。下面介绍一下如何配置EventBus对象。
4. 配置EventBus对象
上面的例子中,我们都是通过EventBus.getDefault()获取一个全局共享的EventBus对象。实际上,我们可以自行创建一个EventBus对象,并对其进行配置。例如:
EventBus eventBus = EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.build();
默认情况下,如果我们发布某个事件,但是该事件没有订阅者。那么EventBus会在日志中输出并提示我们。如果我们不希望出现该默认行为,可以在创建eventBus对象前通过logNoSubscriberMessages方法禁用之。sendNoSubscriberEvent方法也是如此。
再比如,如果我们订阅者方法中出现异常,EventBus会帮助我们捕获该异常并发送SubscriberExceptionEvent 事件。这可能会在开发阶段会隐藏部分问题,我们也可以通过配置EventBus对象的方式禁用这种行为,例如:
EventBus eventBus = EventBus.builder().throwSubscriberException(true).build();
我们还可以对通过EventBus.getDefault()获取的全局共享默认对象进行配置。例如:
EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
但需要注意的是,对默认EventBus对象的配置需要在使用该对象之前完成,否则会抛出异常。这是为了保证默认对象全局行为的一致性。因此,在Application类中修改默认对象的配置是不错的地方。
更多配置方法见此文档。
5. 黏性事件(Sticky Events)
在某些情况下,新创建的订阅者可能对某个过去最近一次发送的某个特定的事件感兴趣,希望能够在订阅后立即收到该事件。这种情况在EventBus中可以使用黏性事件来完成。
EventBus会将最后一次发送的特定类型的黏性事件保存在内存中,这样新的“感兴趣”的订阅者能够立即接收到该事件。例如,以下代码将会发送一个类型为MessageEvent的黏性事件:
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
假如说,之后某个新的Activity被加载并进入与用户交互的状态。并且想要立刻获取到最近一次发送的类型为MessageEvent的黏性事件,可以这样写:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
// UI updates must run on MainThread
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
textField.setText(event.message);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
在接收时,我们只需要在@Subscribe注解中设置sticky为true。
上面的代码中,在订阅器订阅成功后会立即接收到黏性事件,并触发事件处理逻辑。但是有时候,我们希望能够主动的获取某个黏性事件,然后对其进行处理,可以这样写:
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
}
上面代码中,我们通过getStickyEvent主动获取最近一次发送的MessageEvent事件。然后在执行相应的逻辑后(相当于消费了这个黏性事件),通过EventBus.getDefault().removeStickyEvent移除了该黏性事件。这样,其他订阅者将不会再收到这个已发送的黏性事件。
6. 事件优先级和取消事件
在订阅事件时,可以指定优先级。不指定的话,默认优先级为0。例如:
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
...
}
对于同一个事件,具有相同ThreadMode的订阅器(注意这两个前提),高优先级的订阅器将会优先接收到事件。
如果高优先级的订阅器接收到事件后,不希望该事件继续传递给其他低优先级的订阅器,可以取消该事件的向下分发传递。例如:
// Called in the same thread (default)
@Subscribe(priority = 10);
public void onEvent(MessageEvent event){
// Process the event
...
// Prevent delivery to other subscribers
EventBus.getDefault().cancelEventDelivery(event) ;
}
(注:只有处于ThreadMode.POSTING模式的订阅器,才能够取消事件分发!)
7. 订阅者索引(Subscriber Index)
订阅者索引是一个可选的优化项,它避免了在运行时通过反射查找订阅方法的昂贵开销。
使用订阅者索引前需要确保如下规则:
如果以上规则没有被满足,那么使用订阅者索引时将会退化到使用运行时反射查找的方式。
生成订阅者索引:
(1)Java:使用注解处理器(annotationProcessor)
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
def eventbus_version = '3.2.0'
implementation "org.greenrobot:eventbus:$eventbus_version"
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
}
(2)kotlin:使用kapt
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
dependencies {
def eventbus_version = '3.2.0'
implementation "org.greenrobot:eventbus:$eventbus_version"
kapt "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
}
kapt {
arguments {
arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
}
}
在build项目之前,确保已经存在@Subscribe修饰的订阅方法。然后build项目,将会生成一个MyEventBusIndex类。
在Application类中,我们需要在创建EventBus对象时,将MyEventBusIndex实例通过addIndex的方式添加进去:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
对于默认的EventBus对象:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
8. 混淆规则
如果在项目中使用了R8或者是ProGuard,需要在proguard-rules.pro文件中添加如下规则:
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
(java.lang.Throwable);
}
9. AsyncExecutor
有时候,某个线程中的某段代码执行出现异常并抛出,我们可能会希望能够将该异常捕获并发送一个全局事件。这样就可以通过订阅者在其他地方或线程中处理该异常。这时就可以使用AsyncExecutor,它能够让我们异步的处理全局异常,并简化异步处理的流程。使用方法如下:
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
}