♪(^∇^*) 五一假期在家无事,新项目中用的是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

二、快速使用

准备工作

添加依赖(两种方式):

  1. //Via Gradle
    implementation 'org.greenrobot:eventbus:3.1.1'
  1. <!--Via Maven-->
  2. <dependency>
  3. <groupId>org.greenrobot</groupId>
  4. <artifactId>eventbus</artifactId>
  5. <version>3.1.1</version>
  6. </dependency>

第一步 定义事件

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

  1. public class MessageEvent {
  2. public final String message;
  3.  
  4. public MessageEvent(String message) {
  5. this.message = message;
  6. }
  7. }

第二步 准备订阅者-Subscriber

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

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

  1. // This method will be called when a MessageEvent is posted (in the UI thread for Toast)
  2. @Subscribe(threadMode = ThreadMode.MAIN)
  3. public void onMessageEvent(MessageEvent event) {
  4. Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
  5. }
  6.  
  7. // This method will be called when a SomeOtherEvent is posted
  8. @Subscribe
  9. public void handleSomethingElse(SomeOtherEvent event) {
  10. doSomethingWith(event);
  11. }

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

  1. @Override
  2. public void onStart() {
  3. super.onStart();
  4. EventBus.getDefault().register(this);
  5. }
  6.  
  7. @Override
  8. public void onStop() {
  9. EventBus.getDefault().unregister(this);      //注:在super之前注销。
  10. super.onStop();
  11. }

第三步 发布事件

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

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

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

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

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

第一种  ThreadMode: POSTING

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

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

第二种  ThreadMode:MAIN

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

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

第三种  ThreadMode:MAIN_ORDERED

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

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

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

第四种  ThreadMode:BACKGROUND

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

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

第五种  ThreadMode:ASYNC

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

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

四、配置(Configuration)

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

  1. EventBus eventBus = EventBus.builder()
  2. .logNoSubscriberMessages(false)
  3. .sendNoSubscriberEvent(false)
  4. .build();

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

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

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

配置默认的EventBus实例

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

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

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

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

五、粘性事件(Sticky Events)

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

示例

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

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

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

  1. @Override
  2. public void onStart() {
  3. super.onStart();
  4. EventBus.getDefault().register(this);
  5. }
  6.  
  7. // UI updates must run on MainThread
  8. @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
  9. public void onEvent(MessageEvent event) {
  10. textField.setText(event.message);
  11. }
  12.  
  13. @Override
  14. public void onStop() {
  15. EventBus.getDefault().unregister(this);
  16. super.onStop();
  17. }

手动获取和删除粘性事件

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

  1. MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
  2. // Better check that an event was actually posted before
  3. if(stickyEvent != null) {
  4. // "Consume" the sticky event
  5. EventBus.getDefault().removeStickyEvent(stickyEvent);
  6. // Now do something with it
  7. }

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

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

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

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

订阅者优先级(Subscriber Priorities)

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

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

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

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

取消事件传递(Cancelling event delivery)

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

  1. // Called in the same thread (default)
  2. @Subscribe
  3. public void onEvent(MessageEvent event){
  4. // Process the event
  5. ...
  6. // Prevent delivery to other subscribers
  7. EventBus.getDefault().cancelEventDelivery(event) ;
  8. }

事件通常被高优先级的订阅者取消。取消仅限于在发布线程(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构建脚本中:

  1. android {
  2. defaultConfig {
         /*ReBuild Project之后会在build文件夹下生成MyEventBusIndex.class*/
  3. javaCompileOptions {
  4. annotationProcessorOptions {
  5. arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
  6. }
  7. }
  8. }
  9. }
  10.  
  11. dependencies {
  12. implementation 'org.greenrobot:eventbus:3.1.1'
  13. annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
  14. }

使用kapt

如果你想在Kotlin代码中使用EventBus,则需要使用 kapt   代替 annotationProcessor  :

  1. apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
  2.  
  3. dependencies {
  4. implementation 'org.greenrobot:eventbus:3.1.1'
  5. kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
  6. }
  7.  
  8. kapt {
  9. arguments {
  10. arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
  11. }
  12. }

使用android-apt

如果上述不适用于你,则可以使用android-apt  Gradle插件将EventBus注释处理器添加到你的构建中。将以下部分添加到你的Gradle构建脚本中:

  1. buildscript {
  2. dependencies {
  3. classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  4. }
  5. }
  6.  
  7. apply plugin: 'com.neenbedankt.android-apt'
  8.  
  9. dependencies {
  10. compile 'org.greenrobot:eventbus:3.1.1'
  11. apt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
  12. }
  13.  
  14. apt {
  15. arguments {
  16. eventBusIndex "com.example.myapp.MyEventBusIndex"
  17. }
  18. }

如何使用索引

成功构建项目后,将会生成使用eventBusIndex指定的类 。然后当设置EventBus通过它像这样:

  1. EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

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

  1. EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
  2. // Now the default instance uses the given index. Use it like this:
  3. EventBus eventBus = EventBus.getDefault();

为你的Libraries加上索引

您可以将相同的原则应用于作为库的一部分的代码(而不是最终应用程序)。这样,您可能有多个索引类,您可以在EventBus设置期间添加多个索引类,例如:
  1. EventBus eventBus = EventBus.builder()
  2. .addIndex(new MyEventBusAppIndex())
  3. .addIndex(new MyEventBusLibIndex()).build();

八、混淆(ProGuard)

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

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

  1. -keepattributes *Annotation*
  2. -keepclassmembers class * {
  3. @org.greenrobot.eventbus.Subscribe <methods>;
  4. }
  5. -keep enum org.greenrobot.eventbus.ThreadMode { *; }
  6.  
  7. # Only required if you use AsyncExecutor
  8. -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
  9. <init>(java.lang.Throwable);
  10. }

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

九、AsyncExecutor

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

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

通常,你调用 AsyncExecutor 。create ()创建一个实例并将其保存在Application范围中。然后执行一些操作,实现 RunnableEx接口并将其传递给AsyncExecutor的execute方法。与Runnable不同 , RunnableEx可能会抛出异常。

如果 RunnableEx实现引发异常,它将被捕获并包装到ThrowableFailureEvent中,该事件将被公布。

执行示例:

  1. AsyncExecutor.create().execute(
  2. new AsyncExecutor.RunnableEx() {
  3. @Override
  4. public void run() throws LoginException {
  5. // No need to catch any Exception (here: LoginException)
  6. remote.login();
  7. EventBus.getDefault().postSticky(new LoggedInEvent());
  8. }
  9. }
  10. );

接收部分示例:

  1. @Subscribe(threadMode = ThreadMode.MAIN)
  2. public void handleLoginEvent(LoggedInEvent event) {
  3. // do something
  4. }
  5.  
  6. @Subscribe(threadMode = ThreadMode.MAIN)
  7. public void handleFailureEvent(ThrowableFailureEvent event) {
  8. // do something
  9. }

AsyncExecutor Builder

 如果你想定制你的AsyncExecutor实例,请调用静态方法 AsyncExecutor.builder() 。它将返回一个构建器,它允许您自定义EventBus实例,线程池和失败事件的类

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

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

 
 源码地址:eventbus3demo
 

Android EventBus3.x 使用详解的更多相关文章

  1. 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING

    <Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th  2014 Email:skyseraph00@163.com 更多精彩请直接 ...

  2. Android开发之InstanceState详解

    Android开发之InstanceState详解   本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceS ...

  3. ANDROID L——Material Design详解(UI控件)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...

  4. android bundle存放数据详解

    转载自:android bundle存放数据详解 正如大家所知道,Activity之间传递数据,是将数据存放在Intent或者Bundle中 例如: 将数据存放倒Intent中传递: 将数据放到Bun ...

  5. Cordova 打包 Android release app 过程详解

    Cordova 打包 Android release app 过程详解 时间 -- :: SegmentFault 原文 https://segmentfault.com/a/119000000517 ...

  6. Android中Service(服务)详解

    http://blog.csdn.net/ryantang03/article/details/7770939 Android中Service(服务)详解 标签: serviceandroidappl ...

  7. 给 Android 开发者的 RxJava 详解

    我从去年开始使用 RxJava ,到现在一年多了.今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 .而最近这几个 ...

  8. Android中mesure过程详解

    我们在编写layout的xml文件时会碰到layout_width和layout_height两个属性,对于这两个属性我们有三种选择:赋值成具体的数值,match_parent或者wrap_conte ...

  9. Android中Intent组件详解

    Intent是不同组件之间相互通讯的纽带,封装了不同组件之间通讯的条件.Intent本身是定义为一个类别(Class),一个Intent对象表达一个目的(Goal)或期望(Expectation),叙 ...

随机推荐

  1. Spark2.X环境准备、编译部署及运行

    下载地址 :https://www.apache.org/dyn/closer.lua/spark/spark-2.2.0/spark-2.2.0.tgz 我们把spark放在节点2上 解压 下面我们 ...

  2. ngModel缺省是by reference,

    1) 缺省是 by reference, not value,  ngModel, ngOptions等都一样 2) 如果要 track value 用 "track by",   ...

  3. 对KVM虚拟机进行cpu pinning配置的方法

    这篇文章主要介绍了对KVM虚拟机进行cpu pinning配置的方法,通过文中的各种virsh命令可进行操作,需要的朋友可以参考下 首先需求了解基本的信息 1 宿主机CPU特性查看 使用virsh n ...

  4. C#的两种类据类型:值类型和引用类型

    注:引用类型相等赋值是地址赋值,不是值赋值. 什么是值类型,什么是引用类型 概念:值类型直接存储其值,而引用类型存储对其值的引用.部署:托管堆上部署了所有引用类型. 引用类型:基类为Objcet 值类 ...

  5. es6基础(7)--函数扩展

    { //有默认值的后面如果有参数必须要有默认值 function test(x,y="world"){ console.log(x,y) } test('hello');//hel ...

  6. day9笔记整理,记忆

    函数的使用:一 定义函数的三种形式 1.1 无参函数 1.2 有参函数 1.3 空函数二 调用函数的三种形式 2.1 语句形式    def foo():   print('from foo')    ...

  7. puppeteer 的PDD反爬经历

    使用puppeteer 爬取PDD数据时出现要求登录,以前是没有这问题的. 尝试多种方式如果: 变更UA 变更代理IP 变更Chromium版本(当然最终就是该问题的原因,但是因为版本跨度太大没有测试 ...

  8. spring boot整合quartz实现多个定时任务

        版权声明:本文为博主原创文章,转载请注明出处. https://blog.csdn.net/liuchuanhong1/article/details/78543574 最近收到了很多封邮件, ...

  9. oracle提高查询效率的34个方面全解析

    oracle提高查询效率的34个方面全解析   在一个数据库中进行操作的时候,效率是很重要的,那么,如何提高oracle的查询效率呢?笔者将从以下几个方面进行详细解析: 1.选择最有效率的表名顺序(只 ...

  10. 关于{get;set;}访问器

    /// <summary> /// 此视频更新时间/创建时间 [生成时间,不手填] /// </summary> public System.String CreateTime ...