介绍

EventBus是一个使用发布者/订阅者模式 并且低耦合的Android开源库,。 EventBus只需几行代码即可实现中央通信解耦类:简化代码,删除依赖关系,加快应用程序开发速度。

这里写图片描述

优势

  • 简化组件通信
  • 解耦事件发送端和接收端
  • 在Activity,Fragment和后天线程之间运行良好
  • 避免复杂且易出错的依赖问题和生命周期问题
  • 运行速度快。为了高性能专门做过优化
  • jar包小(<50K)
  • 在实践中已通过100,000,000次安装的应用程序证明
  • 具有指定分发线程和用户优先级等高级特性

特性

  • 简单但强大:EventBus是一个微小的库,具有超级容易学习的API。然而,您的软件架构可能因组件解耦而受益:订阅者在使用事件时不用关心这个事件是谁发送的。

  • 大量验证:EventBus是最常用的Android库之一:成千上万的应用程序使用EventBus,其中不乏非常受欢迎的应用程序。我们认为差不多有十亿应用程序使用EventBus。

  • 高性能:Android系统上,性能尤为重要。 EventBus被针对性的做了大量的分析和优化;EventBus可能是这类开源库中速度最快的 解决方案。

  • 基于API的便捷注解(不牺牲性能):只需将@Subscribe注解添加订阅方法即可。由于构建的时候花费了时间来建立注解的索引,所以EventBus不需要在应用程序的运行时间执行注释反射,注解反射的方式在Android上相当慢。

  • Android主线程发送:当与UI交互时,EventBus可以在主线程中传递事件,而不用去关心事件是从如何发布的。

  • 后台线程发送:如果您的订阅者长时间运行任务,EventBus也可以使用后台线程来避免UI阻塞。

  • 事件和订阅者继承:在EventBus中,面向对象的范例适用于事件和订阅者类。让我们假设事件类A是B的父类。类型B的发布事件也将被发布到对A感兴趣的订阅者。类似地,考虑订阅类的继承。

  • 零配置:您可以从代码中的任何地方立即使用默认的EventBus实例。

  • 可配置:要根据需要调整EventBus,可以使用构建器模式调整其行为。

添加EventBus到工程

EventBus在JCenter和Maven Central上可用,因此只需将依赖项添加到项目中。

  • Gradle
compile 'org.greenrobot:eventbus:3.0.0'
  • Maven
<dependency>
    <groupId>org.greenrobot</groupId>
    <artifactId>eventbus</artifactId>
    <version>3.0.0</version>
</dependency>

开始使用EventBus

一、简单的三步教你使用EvnetBus

定义事件

事件是纯Java对象,没有其他的特殊要求

public class MessageEvent {

    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}
准备订阅者

订阅者需要实现在事件发布时将被调用的事件处理方法(也称为“订阅方法”)。 这些订阅方法将用@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);
}

订阅者还需要向总线注册和注销。 只有订阅者注册后,他们才会收到事件。 在Android的在活动和片段中,通常应该根据其生命周期进行注册。 对于大多数情况下在onStart中注册,在onStop中销毁就OK了。

发布事件

在代码的任意地方发布事件,所有与当前事件匹配且注册过的订阅者都将会收到事件。

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

二、传递线程

EventBus可以为你处理线程:事件可以发布在不同于发布线程的线程中。事件可以发布在和发布线程不同的线程里。一个普遍的场景就是处理UI的变化。在Android系统中,UI更改必须在UI(主)线程中完成。另一方面,网络或者任何耗时的任务,都不允许在主线程中执行。EventBus帮助你处理这些任务并且与UI线程同步(无需深入了解线程转换,使用AsyncTask等)。

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

ThreadMode: POSTING

订阅者将会在事件发布的线程中被调用。这是默认模式。事件的传递是同步完成的,一旦事件发布完成,所有的订阅者都将会被调用。由于完全避免了线程切换,这种模式的开销是最小的。所以,针对那些不需要主线程并且能在很短时间内完成的简单任务,我们推荐使用这种模式。这种模式下的事件处理方法必须快速返回,避免阻塞发布事件的线程,因为这个线程有可能是主线程。 例如:

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

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

// Called in Android UI's main thread
@Subscribe(threadMode = ThreadMode.MAIN)
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);
}

三、配置

通过EventBusBuilder,我们可以配置EventBus的各个方面。 例如,下面是如何构建一个当发送的事件没有订阅者时不报异常的EventBus

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

另外一个例子是,如何构建一个在订阅者排除异常时报错的EventBus

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

注意:默认情况下,EventBus捕获从订阅者方法抛出的异常,并发送不强制要求处理的SubscriberExceptionEvent。

使用EventBus.getDefault()是一种从应用程序的任何位置获取共享EventBus实例的简单方法。 EventBusBuilder还允许使用方法installDefaultEventBus()配置此默认实例。

例如,可以配置默认的EventBus实例来重新抛出在订阅方法中发生的异常。 但是让我们只对DEBUG构建,因为这可能会崩溃的应用程序异常

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

注意:在第一次使用默认EventBus实例之前,只能执行一次。 后续调用installDefaultEventBus()将抛出异常。 这可确保应用程序中的行为一致。 您的应用程序类是在使用之前配置默认EventBus实例的不错地方。

四、粘性事件

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

粘性示例

下面表示,一个粘性事件发布了一段时间以前:

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

现在一个新的活动开始。 在注册期间,所有粘性订阅者方法将立即获得先前发布的粘性事件:

@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
}

五、优先级和事件取消

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

订阅者优先级

你可以通过在注册期间为订阅者提供优先级来更改事件传递的顺序。

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

在相同的传递线程(ThreadMode)中,较高优先级的订阅者将在其他优先级较低的订阅者之前接收事件。 默认优先级为0。 注意:优先级不影响不同线程模式的订阅者之间的传递顺序!

取消事件传送

你可以通过从订阅者的事件处理方法调用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)中运行的事件处理方法。

六、订阅者索引

订阅者索引是EventBus 3的一个新功能。它是一个可选的优化,以加速初始订阅者注册。

订阅者索引可以在构建时通过EventBus注解创建。 因为不需要使用索引,为了更好的性能建议在Android上使用。

索引前提条件

注意,只有@Subscriber方法可以索引,其中订阅者和事件类是public的。 此外,由于Java的注释处理本身的技术限制,@Subscribe注释不能在匿名类中识别。

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

如何生成索引
  • 使用注解 如果你不使用Android Gradle插件版本2.2.0或更高版本,请使用配置与android-apt。 要启用索引生成,需要使用annotationProcessor属性将EventBus注释处理器添加到构建中。 还要设置参数eventBusIndex来指定要生成的索引的完全限定类。 例如,将以下部分添加到Gradle构建脚本中:
android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
  • 使用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.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();
索引你的开源库

你可以将相同的原则应用于作为库(而不是最终应用程序)的一部分的代码。 这样,您可能有多个索引类,您可以在EventBus设置期间添加所有索引类,例如:

EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex())
    .addIndex(new MyEventBusLibIndex()).build();

七、ProGuard

ProGuard模糊方法名称并可能删除不被调用的方法(死代码删除)。 因为Subscriber方法不是直接调用,ProGuard假定它们未被使用。 因此,如果您启用ProGuard缩减,您必须告诉Proguard保留这些Subscriber方法。

在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会将这些异常包装在事件中,并自动发布。

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

通常,调用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实例,线程池和失败事件的类。

另一个定制选项是执行范围,它提供故障事件上下文信息。 例如,失败事件可能仅与特定的Activity实例或类相关。

如果您的自定义失败事件类实现了HasExecutionScope接口,AsyncExecutor将自动设置执行范围。 像这样,您的订阅者可以查询失败事件的执行范围,并根据它做出反应。

原文出处

翻译原文: http://greenrobot.org/eventbus/

results matching ""

    No results matching ""