Android EventBus3.x 使用详解

时间:2023-07-25 20:04:07

♪(^∇^*) 五一假期在家无事,新项目中用的是RxJava2+EventBus感觉还不错,趁这闲暇总结下EventBus。

一、概要简述

  EventBus是一个基于观察者模式的Android事件发布/订阅框架,通过解耦发布者和订阅者简化Android事件传递,这里的事件可以理解为消息。事件传递既可以用于Android四大组件间通讯,也可以用于异步线程和主线程间通讯等。
  EventBus的出现,是为了解决传统的通过Interface的事件传递所出现的回调地狱的问题,相比之下EventBus的有点是代码简洁,使用简单,并将事件发布和 订阅充分解耦。

  EventBus由三部分组成:event事件、subscriber订阅者、publisher发布者。

  EventBus 官网地址:http://greenrobot.org/eventbus/

  EventBus GitHub :https://github.com/greenrobot/EventBus

参考:

    EventBus 使用详解(推荐) :https://blog.csdn.net/ljd2038/article/details/50866294

  慕课网 Android事件分发库的使用 :https://www.imooc.com/learn/871

源码地址:eventbus3demo

二、快速使用

准备工作

添加依赖(两种方式):

//Via Gradle
implementation 'org.greenrobot:eventbus:3.1.1'
<!--Via Maven-->
<dependency>
<groupId>org.greenrobot</groupId>
<artifactId>eventbus</artifactId>
<version>3.1.1</version>
</dependency>

第一步 定义事件

事件是POJO(普通的旧Java对象),没有任何特定的要求。

public class MessageEvent {
public final String message; public MessageEvent(String message) {
this.message = message;
}
}

第二步 准备订阅者-Subscriber

订阅者实现事件处理方法,当事件发布时将被调用。这些是使用@Subscribe注释定义的。

请注意,通过EventBus 3,可以*选择方法名称(不像EventBus 2中的命名约定)。

// 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);
}

Subscriber需要registe themselves和unregist from bus ,通常情况下,我们在Activity或Fragment的onStart / onStop执行注册和注销操作。

@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
} @Override
public void onStop() {
EventBus.getDefault().unregister(this);      //注:在super之前注销。
super.onStop();
}

第三步 发布事件

从项目中的任何地方发布事件(post events)。所有注册了当前匹配事件类型的订阅者都会收到它。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

三、交付线程(Delivery Threads) -  五种线程模型(ThreadMode)

EventBus处理线程:事件的订阅者和发布者不在同一个线程。一个常见的用例是处理UI更新。在Android中,UI更改必须在UI(主)线程中完成。另一方面,网络或任何耗时的任务不得在主线程上运行。EventBus可以帮助你处理这些任务并与UI线程同步(无需深入研究线程转换,使用AsyncTask等)。

在EventBus中,你可以使用五个ThreadMode中的一个来定义将调用事件处理方法的线程。

第一种  ThreadMode: POSTING

默认设置。该模式下,订阅者(Subscriber)将被调用在发布该事件处的同一线程中。事件传递是同步完成的,所有订阅者在发布完成后都会被调用。这个ThreadMode意味着最小的开销,因为它避免了线程的完全切换。因此,对于已完成的简单任务而言,推荐使用该模式,因为只需很短的时间而且不需要主线程。使用此模式的事件处理程序应该快速返回以避免阻塞发布线程(可能是主线程)。例:

// Called in the same thread (default)
// ThreadMode is optional here
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
log(event.message);
}

第二种  ThreadMode:MAIN

订阅者将在主线程(UI线程)中被调用。如果发布线程是主线程,则会直接调用事件处理程序方法(与ThreadMode.POSTING中描述的相同)。使用此模式的事件处理程序必须快速返回以避免阻塞主线程。例:

// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}

第三种  ThreadMode:MAIN_ORDERED

订阅者将在主线程中被调用。该事件总是排队等待以后发送给订阅者,因此发出的调用将立即返回。这为事件处理提供了更严格和更一致的顺序(因此名称MAIN_ORDERED)。举个栗子?如果你在带有主线程模式的事件处理程序中发布另一个事件,第二个事件处理程序将在第一个事件之前完成(因为它是同步调用的——将其与方法调用进行比较)。在主序中,第一个事件处理程序将完成,然后第二个事件处理程序将在稍后的时间点被调用(只要主线程有容量)。

使用此模式的事件处理程序必须快速返回以避免阻塞主线程。例:

// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}

第四种  ThreadMode:BACKGROUND

订阅者将在后台线程中调用。如果发布线程不是主线程,则会在发布线程中直接调用事件处理程序方法。如果发布线程是主线程,则EventBus使用单个后台线程来按顺序发送所有事件。使用此模式的事件处理程序应尽快返回以避免阻塞后台线程。

// Called in the background thread
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}

第五种  ThreadMode:ASYNC

事件处理方法在单独的线程中调用。这总是独立于发布线程和主线程。发布事件永远不会等待使用此模式的事件处理方法。如果事件处理可能需要一些时间,例如网络访问。避免同时触发大量长时间运行的异步处理程序方法来限制并发线程的数量。EventBus使用线程池有效地重用已完成异步事件处理程序通知中的线程。

// Called in a separate thread
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}

四、配置(Configuration)

EventBusBuilder类配置EventBus的各个方面。例如,以下是如何构建EventBus,以便在没有订阅者的情况下发布事件,还能保持安静状态:

EventBus eventBus = EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.build();

另一个例子是当用户抛出异常时失败:

EventBus eventBus = EventBus.builder().throwSubscriberException(true).build();

注意:默认情况下,EventBus捕获从订阅者方法抛出的异常,并发送可能但不必处理的SubscriberExceptionEvent。

配置默认的EventBus实例

使用EventBus.getDefault()是从应用程序中的任何位置获取共享EventBus实例的简单方法。EventBusBuilder也允许使用方法installDefaultEventBus()来配置这个默认实例 。

例如,可以配置默认的EventBus实例来重新引发用户方法中发生的异常。但是,让我们仅针对DEBUG构建,因为这可能会导致应用程序异常崩溃:

EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();

注意:在第一次使用默认的EventBus实例之前,这只能完成一次。随后对installDefaultEventBus ()的调用   将引发异常。这确保了你的应用程序的一致行为。你的应用程序类是在使用之前配置默认EventBus实例的好地方。

五、粘性事件(Sticky Events)

一些事件在事件发布后携带有趣的信息。例如,一个事件表示一些初始化完成。或者如果你有一些传感器或位置数据,并且想要保留最新值。你可以使用粘性事件而不是实现自己的缓存。所以EventBus在内存中保存了某种类型的最后一个粘性事件。然后,粘性事件可以传递给订阅者或显式查询。因此,你不需要任何特殊的逻辑来考虑已有的数据。

示例

比方说,先发送一个粘性事件:

EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

现在开启一个新的Activity,在注册期间,所有粘性订阅者方法将立即获取之前发布的粘性事件

@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();
}

手动获取和删除粘性事件

如你所见,最后的粘性事件会在注册时自动发送给匹配的订阅者。但有时候手动检查粘性事件可能会更方便。此外,可能需要删除(消耗)粘性事件,以使它们不再被传递。例:

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
}

removeStickyEvent方法 被重载:当你传入类时,它将返回先前持有的粘滞事件。使用这个变体,我们可以改进前面的例子:

MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// Now do something with it
}

六、优先级和事件取消(Priorities and Event Cancellation)

尽管大多数EventBus使用案例不需要优先级,也不需要取消事件,但在某些特殊情况下它们可能会派上用场。例如,如果应用程序处于前台,某个事件可能会触发某些UI逻辑,但如果该应用程序当前对用户不可见,则会作出不同的反应。

订阅者优先级(Subscriber Priorities)

你可以通过在注册期间向订户提供优先级来更改事件交付的顺序。

@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
...
}

在同一个传递线程(ThreadMode)内,更高优先级的订阅者将在优先级较低的其他人之前接收事件。默认优先级是0

注意:优先级不会影响具有不同ThreadModes的订户之间的传递顺序

取消事件传递(Cancelling event delivery)

你可以通过从订阅者的事件处理方法调用cancelEventDelivery(Object event )来取消事件传递过程 。任何进一步的事件将被取消,后续订阅者将不会收到活动。

// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
...
// Prevent delivery to other subscribers
EventBus.getDefault().cancelEventDelivery(event) ;
}

事件通常被高优先级的订阅者取消。取消仅限于在发布线程(ThreadMode.PostThread)中运行的事件处理方法。

七、订阅者索引(Subscriber Index)

用户索引是EventBus 3的一项新功能。它是可选的优化,可加速初始用户注册

订阅者索引可以在构建时使用EventBus注释处理器创建。尽管没有要求必须使用索引,但为了获得最佳性能建议在Android上使用

索引先决条件(Index Preconditions)

请注意,只有@Subscriber方法可以为公开的(public订阅者(subscriber )和事件类(event class创建索引。另外,由于Java注释处理本身的技术限制,@Subscribe注释在匿名类中不被识别

当EventBus不能使用索引时,它将在运行时自动回退到反射。因此它仍然可以工作,只是慢一点。

如何生成索引 - 三种方式

使用annotationProcessor

如果你未使用Android Gradle插件2.2.0或更高版本,请使用android-apt配置。

要启用索引生成,你需要使用annotationProcessor属性将EventBus注释处理器添加到你的构建中 。还要设置一个参数eventBusIndex来指定要生成的索引的完全限定类。例如,将以下部分添加到你的Gradle构建脚本中:

android {
defaultConfig {
     /*ReBuild Project之后会在build文件夹下生成MyEventBusIndex.class*/
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注释处理器添加到你的构建中。将以下部分添加到你的Gradle构建脚本中:

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 = EventBus.builder().addIndex(new MyEventBusIndex()).build();

或者,如果你想在整个应用程序中使用默认实例:

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 = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();

八、混淆(ProGuard)

ProGuard混淆了方法名称,并可能会删除未调用的方法(去除死代码)。由于不直接调用订阅者方法,因此ProGuard假定它们未被使用。因此,如果启用ProGuard缩小功能,则必须通知ProGuard保留这些订阅者方法。

在你的ProGuard配置文件(proguard.cfg)中使用以下规则来防止删除订阅者:

-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);
}

注意:无论您是否使用订阅者索引,您都将需要此配置。

九、AsyncExecutor

AsyncExecutor就像一个线程池,但具有失败(异常)处理能力。失败引发异常,AsyncExecutor会将这些异常封装在自动发布的事件中。

免责声明:AsyncExecutor是一个非核心实用程序类。它可以为后台线程中的错误处理节省一些代码,但它不是一个核心的EventBus类。

通常,你调用 AsyncExecutor 。create ()创建一个实例并将其保存在Application范围中。然后执行一些操作,实现 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将自动设置执行范围。像这样,你的订阅者可以查询失败事件的执行范围并根据它做出反应。

 源码地址:eventbus3demo