android 初识EventBus
title: android 初识EventBus
date: 2016-04-17
tags: eventbus
本文档基于EventBus 3.0.0
EventBus是一个发布/订阅事件总线,用来优化android程序。
EventBus是什么
- 简化组件之间通信。
- 解耦事件的发送者和接受者。
- 在Activities,Fragments和后台线程之间表现的很好
- 避免复杂的和容易出错的依赖和生命周期
- 使代码简单
- 快,高性能:特别是在注重性能的Android上。也许在其同类的解决方案是最快的。
- 小(jar包小于50K),但是强大:EventBus是一个很小的库,它的API超级简单。但是你的软件架构会非常受益于组件解耦:当使用事件的时候,订阅者不需要知道发送者是谁。
- 在实践中被100,000,000+的应用安装测试。
- 有在线程间传递,订阅优先级等高级特性
- 基于方便的注解(不牺牲性能):只需要通过在你的订阅方法放置@Subscribe注解。因为是在编译的时候索引注解,EventBus不需要在app运行时间做注解反射(在android上会很慢)。
- android主线程传递:当和UI交互的时候,无论这个事件是怎么提交的,EventBus都可以在主线程传递事件。
- 后台线程传递:如果订阅者运行长时间的任务,EventBus也可以使用后台线程来避免UI阻塞。
- 事件和订阅继承:在EventBus中,事件和订阅类都是面向对象的范例。事件A是B的父类。提交B类型的事件也会提交给对A感兴趣的订阅者。类似的订阅者类也是如此。
- 0配置:在代码中任何位置可以立刻使用一个默认的EventBus实例。
- 可配置:可以按需求调整EventBus,可以使用建造者模式调整行为。
- EventBus和Android的广播和Intent系统有什么不同
不像Android的广播和intent系统,EventBus使用标准的java类作为事件并且提供更多的方便的API。EventBus更多的使用场景是不想设置麻烦的intent,设置intent extras,实现广播接收者,再提取intent extras。而且开销更低。
添加到你的工程
EventBus可以从JCenter和Maven中央仓库获取,所以只需在gradle脚本中添加这个依赖:
complie 'org.greenrobot:eventbus:3.0.0'
开始使用EventBus
使用EventBus只需3步。在此之前先在Gradle脚本中添加依赖。
complie 'org.greenrobot:eventbus:3.0.0'
第一步:定义事件
事件是POJO(plain old java object)类型,不需要什么特别的需求
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
第二步:准备订阅者
订阅者实现事件处理方法(也叫做订阅者方法),这个方法会在事件提交的时候被调用。这些是使用@Subscribe注解定义的。请注意EventBus 3的方法名字可以自由选择(不像EventBus 2中约束的那样)。
// 当一个Message Event提交的时候这个方法会被调用
@Subscribe
public void onMessageEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// 当一个SomeOtherEvent被提交的时候这个方法被调用。
@Subscribe
public void handleSomethingElse(SomeOtherEvent event){
doSomethingWith(event);
}
订阅者也需要在bus中注册和注销。只有在订阅者注册的时候,他们才会收到事件。在Android中,Activities和Fragments通常绑定他们的生命周期.
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
第三步:提交事件
在代码中任意位置提交事件。所有当前注册的匹配事件类型的订阅者都会收到事件。
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
线程间传递(线程模式)
事件可以在不同线程间传递。典型的使用就是用来处理UI改变,网络操作,或者耗时的操作。EventBus可以处理这些任务并同步UI线程(不必再考虑线程转换,使用AsyncTask,等)。
有四种线程模式:
线程模式:POSTING
默认情况下,订阅者在被提交事件的线程被调用。事件同步完成传递,一旦事件提交所有的订阅者都会被调用。因为避免了线程切换,这种模式意味着开销最小。所以对那些花费很短的时间来完成,不需要在主线程中的简单的任务,推荐使用这种模式。事件处理程序使用这种模式应该是很快能返回的,避免阻塞提交线程比如主线程。比如:
@Subscribe(threadMode = ThreadMode.POSTING) // ThreadMode is optional here
public void onMessage(MessageEvent event) {
log(event.message);
}
线程模式:MAIN
订阅者会在主线程被调用。如果提交的线程是主线程,事件处理方法会直接被调用(就像Thread.POSTING描述的那样同步)。事件处理程序使用这个模式必须能够快速返回避免阻塞主线程。比如:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
textField.setText(event.message);
}
线程模式:BACKGROUND
订阅者会在后台线程被调用。如果不是在主线程提交,事件处理方法会直接在提交线程被调用。如果是在主线程提交,EventBus使用一个单独的后台线程按顺序传递所有事件。事件处理使用这种模式应该能够尽快返回避免阻塞后台线程。
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
saveToDisk(event.message);
}
线程模式:ASYNC
事件处理方法会在一个单独的线程被调用。这个线程通常和提交的线程和主线程是独立的。提交的事件从不等待事件处理方法。事件处理方法如果需要花费一段时间比如访问网络应该使用这种模式。避免在同一时间触发大量长时间异步的处理方法来限制并发的线程。EventBus使用一个线程池来有效重复利用线程完成的异步事件处理程序的通知。
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
backend.send(event.message);
}
配置
EventBusBuilder用来配置EventBus。比如,如果一个提交的事件没有订阅者,可以使EventBus保持安静。
EventBus eventBus = EventBus.builder().logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false).build();
另一个例子是当一个订阅者抛出一个异常的失败。注意:默认情况下,EventBus捕获异常从onEvent方法中抛出并且发出一个SubscriberExceptionEvent ,这个事件可以不必处理。
EventBus eventBus = EventBus.builder().throwSubscriberException(true).build();
更多配置,查看官方文档
配置默认EventBus实例
使用EventBus.getDefault()是一种简单的方法来获取共享的EventBus实例。EventBusBuilder也可以使用installDefaultEventBus()方法来配置这个默认的实例。
比如,当在onEvent方法中发生异常的时候,可以配置默认的EventBus实例来重新抛出异常。建议在使用DEBUG模式的时候这么使用,因为这样app会因为这个异常而崩溃。
EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();
注意:只有在默认EventBus实例在第一次使用之前这么配置一次。后续调用installDefaultEventBus() 会抛出异常。这确保应用程序的行为一致。可以在Application类中配置默认的EventBus。
Sticky Event
一些事件携带在事件提交之后仍然感兴趣的信息。比如,一个事件标记一些初始化完成或者一些传感器或位置数据的最新的值。可以使用sticky事件来代替你自己的实现。EventBus在内存中保持某一个类型的最后的sticky事件。这个sticky事件可以传递到订阅者或者也可以被明确的查询。因此,不需要任何特殊的逻辑来考虑已经可用的数据。
Sticky实例
一个sticky事件是一段时间之前被提交的。
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
现在,sticky事件被提交了,一个新的activity启动。在注册过程中所有的sticky的订阅者方法都会立刻获取到之前提交的sticky事件:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
// UI updates must run on MainThread
textField.setText(event.message);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
手动获取和移除sticky事件
就像前一段说的那样,最后的sticky事件在订阅者注册的时候会自动传递。但是,有时候手动检测sticky事件更方便。有时候他们不再传递的时候需要移除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
}
removeStickyEvent方法是超载的:当传入一个类,它会返回之前保持的sticky事件。使用这中变化可以提升之前的例子。
MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
// Now do something with it
}
优先级和事件取消
大部分的EventBus都不需要优先级或事件取消,但是它们在特殊情况下会派上用场。比如,一个app在后台运行的时候一个事件触发了一些UI的逻辑 ,但是如果app当前对用户是不可见的,那么应该有不同的反应。
订阅者优先级
可以通过在注册期间提供的优先级来改变事件传递的顺序。
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}
在相同的传递线程(ThreadMode),高优先级的订阅者接收事件会在低优先级之前。默认优先级是0。
注意:优先级不会对不同ThreadModes订阅者之间的传递顺序有影响。
取消事件传递
在订阅者事件处理方法中通过cancelEventDelivery(Object event)取消事件传递。之后的任何事件传递都会取消:后续的订阅者不会再接收到这个事件。
// Called in the same thread (default)
@Subscribe
public void onEvent(MessageEvent event){
// Process the event
…
EventBus.getDefault().cancelEventDelivery(event) ;
}
事件通常是被优先级高的订阅者取消。取消对于运行在提交线程ThreadMode.PostThread的事件处理方法是被限制的。
订阅者索引
订阅者索引(subscriber index)是EventBus3的新特性。这是个可选的优化项来提速初始化订阅者注册。订阅者索引可以使用EventBus的注解处理器在编译阶段创建。虽然使用索引不是必须的,但是android最佳实践还是推荐使用。
索引先决条件
注意只有订阅者和事件类是public,@Subscriber方法才能被索引。由于java的注解本身处理的技术限制,@Subscribe注解不会被匿名内部类识别。当EventBus不能使用索引的时候,它会自动回退到在运行时起作用。所以它仍然起作用,只是慢了一点。
生成索引
为了能够生成索引,需要在android-apt gradle插件的编译环境添加EventBus注解处理器。还需要传递一个参数为“eventBusIndex”到指定完全限定的想要生成索引的类。添加下面的部分到Gradle编译脚本:
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.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
索引Libraries
应用相同的原理来索引Library的一部分代码(并不是最终的应用程序)。这样,可以会生成多个索引类,在设置EventBus的时候这样使:
EventBus eventBus = EventBus.builder()
.addIndex(new MyEventBusAppIndex())
.addIndex(new MyEventBusLibIndex()).build();
混淆
混淆器混淆方法名称的时候,有可能会移除方法那些没有调用的方法。因为订阅方法没有被直接调用,混淆器会误认为他们没有被使用。在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);
}
注意:不管是否使用索引,都需要这么配置。
AsyncExecutor
免责声明:AsyncExecutor不是一个核心的utility类。在处理后台线程它也许会保存一些错误代码,但是它不是一个核心的EventBus类。
AsyncExecutor像一个线程池,但是有错误(异常)处理。错误会抛出异常,AsyncExecutor会在事件中包裹这些异常。
通常调用AsyncExecutor.create()方法在Application中来创建一个实例。实现RunnableEx接口来执行。RunnableEx和Runnable不一样,RunnableEx会抛出异常。
如果RunnableEx的实现抛出一个异常,它会在ThrowableFailureEvent中捕获到。
执行的例子:
AsyncExecutor.create().execute(
new RunnableEx {
public void run throws LoginException {
// No need to catch any Exception (here: LoginException)
remote.login();
EventBus.getDefault().postSticky(new LoggedInEvent());
}
}
}
接收的例子:
public void onEventMainThread(LoggedInEvent event) {
// Change some UI
}
public void onEventMainThread(ThrowableFailureEvent event) {
// Show error in UI
}
AsyncExecutor生成器
使用静态的Async Executor来自定义Async Executor实例。它会返回一个生成器来自定义EventBus实例,线程池,和失败的事件类。
另一个可以自定义的选项是提供错误事件上下文环境信息的执行范围。比如,一个错误事件可能只关联到一个指定的Activity实例或类。你自定义错误事件实现了HasExecutionScope接口,AsyncExecutor会自动设置执行范围。这样,订阅者可以在它执行范围查询错误的事件并基于它做出相应操作。