共计 10116 个字符,预计需要花费 26 分钟才能阅读完成。
发布 / 订阅事件总线。它可以让我们很轻松的实现在 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}
4. 订阅索引 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();
5. 混淆 ProGuard
-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);
}
6.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 将自动设置执行范围。与此类似,您的用户可以查询其执行范围的失败事件,并根据它做出反应。