Android事件分发之EventBus应用分析

一、引言

第一次使用EventBus是在两年前的预研项目上(注:新技术一般可用于研究或预研项目,若要用于工程项目,需慎重考虑,通常坑是比较多),APP需要各组件、组件与后台线程间进行通信,比如在子线程中进行请求数据,当数据请求完毕后通过Handler或者是广播通知UI,使用Intent、Handler、Broadcast进行模块间通信、模块与后台线程进行通信时,代码量大,而且高度耦合。此时,刚好看到了EventBus就用上了。

二、EventBus介绍

EventBus由GreenRobot组织贡献的一个Android基于观察者模式的事件发布/订阅轻量级框架(约50k的jar包)。

功能:通过解耦发布者和订阅者简化Android事件传递;EventBus可以代替Android传统的Intent,Handler,Broadcast或接口函数,在Fragment,Activity,Service线程之间传递数据,执行方法。简化各组件间的通信,让代码书写变得简单,能有效的分离事件发送方和接收方,能避免复杂和容易出错的依赖性和生命周期问题。

特点:代码简洁,是一种发布订阅设计模式(观察者设计模式)。

GitHub:EventBus

组成:

  • 事件(Event)
  • 事件订阅者(Subscriber)
  • 事件发布者(Publisher)

EventBus给Android开发者世界带来了一种新的框架和思想,就是消息的发布和订阅。这种思想在其后很多框架中都得到了应用。

随着框架的完善,EventBus 3.0 比EventBus 2.x 有很大的优化,详细如下:

  • EventBus 2.x 必须定义以onEvent开头的几个方法,代码中语境比较突兀,且有可能会导致拼写错误,例如数据同步事件
  • EventBus 3.0 函数名字不再受到权限,而且可以在一个函数中体现出在哪个线程执行,并且可指定接收事件的优先级
  • EventBus 2.x 注册方式也比较繁琐。EventBus 3.0 注册方式只有一个
  • EventBus 2.x 是采用反射的方式对整个注册的类的所有方法进行扫描来完成注册,当然会有性能上的影响
  • EventBus 3.0中EventBus提供了EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析、处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快

愿以后越做越好!

二、基本使用

EventBus的使用比较简单,GitHub主页上都介绍的很清楚。

2.1 把EventBus依赖到项目

只需要在build.gradle添加依赖即可以引用

compile 'org.greenrobot:eventbus:3.0.0'     //最新是3.1.1

2.2 定义事件

事件可以是任意普通的Java对象,没有任何特殊的要求。

public class MessageEvent{
    private String message;

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

其实这个类就是一个Bean类,里面定义用来传输的数据的类型。

2.3 订阅事件

订阅者需要定义事件处理方法(也称为订阅者方法)。当发布对应类型的事件时,该方法将被调用。EventBus 3使用 @Subscribe 注解来定义订阅者方法。方法名可以是任意合法的方法名,参数类型为订阅事件的类型。

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.i(TAG, "message is " + event.getMessage());
        // 事件处理
    }

ThreadMode总共四个:

  • MAIN UI主线程
  • POSTING 默认调用方式,在调用post方法的线程执行,避免了线程切换,性能开销最少
  • BACKGROUND 如果调用post方法的线程不是主线程,则直接在该线程执行。如果是主线程,则切换到后台单例线程,多个方法公用同个后台线程,按顺序执行,避免耗时操作
  • ASYNC 开辟新独立线程,用来执行耗时操作,例如网络访问

订阅者还需要在总线上注册,并在不需要时在总线上注销。只有订阅者注册了,它们才会收到事件。在Android中,可以根据Activity或者Fragment的生命周期来注册和注销。例如:

@Override
protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     ......
     EventBus.getDefault().register(this);
}


@Override
protected void onDestroy() {
    super.onDestroy();
    ......
    EventBus.getDefault().unregister(this);
}

 @Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

总之,注册与注销需成双成对出现。

2.4 发布事件

在需要的地方发布事件,所有订阅了该类型事件并已注册的订阅者将收到该事件。

// 发布事件
EventBus.getDefault().post(new MessageEvent("EventBus!"));

EventBus.getDefault().postSticky(new MessageEvent("EventBus!"));//粘性

2.5 取消发布事件

一旦取消了事件的分发,事件将不再继续向下进行分发。

@Subscribe
public void onEvent(MessageEvent event){
    EventBus.getDefault().cancelEventDelivery(event) ;
}

EventBus.getDefault().removeStickyEvent(stickyEvent);

三、原理分析

3.1 粘性事件

如果先发布了事件,然后有订阅者订阅了该事件,那么除非再次发布该事件,否则订阅者将永远接收不到该事件。此时,可以使用粘性事件。发布一个粘性事件之后,EventBus将在内存中缓存该粘性事件。当有订阅者订阅了该粘性事件,订阅者将接收到该事件。

概念:黏性事件就是指在EventBus内部被缓存的那些事件。

原理:EventBus为每个类(class)类型保存了最近一次被发送的事件——sticky。后续被发送过来的相同类型的sticky事件会自动替换之前缓存的事件。当一个监听者向EventBus进行注册时,它会去请求缓存事件。这时,缓存的事件就会立即自动发送给这个监听者,有一定的延时性。

使用场景:Android上跨Activity和Fragment生命周期传递数据这种复杂问题,异步调用等等。

3.2 事件优先级

EventBus支持在定义订阅者方法时指定事件传递的优先级。在EventBus使用过程中一般不需要使用优先级,但可用来处理特殊情况。例如,一个事件在app处于前台时要触发ui逻辑;但当app处于后台,则要有不一样的响应。

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

默认情况下,订阅者方法的事件传递优先级为0。数值越大,优先级越高。在相同的线程模式下,更高优先级的订阅者方法将优先接收到事件。注意:优先级只有在相同的线程模式下才有效。

3.3 订阅者索引

默认情况下,EventBus在查找订阅者方法时采用的是反射。订阅者索引是EventBus 3的一个新特性。它可以加速订阅者的注册,是一个可选的优化。订阅者索引的原理是:使用EventBus的注解处理器在应用构建期间创建订阅者索引类,该类包含了订阅者和订阅者方法的相关信息。EventBus官方推荐在Android中使用订阅者索引以获得最佳的性能。

没有使用到,暂不讨论。

四、总结

如今,EventBus很成熟,使用起来很方便,当代码量变得很多的时候,使用EventBus会简化很多代码;所以,涉及各组件通讯的时候,EventBus是一个不错的选择。


常见问题

1 没成双成对使用,导致消息收不到

注册前先判断是否已注册

if (!EventBus.getDefault().isRegistered(this)) {
    EventBus.getDefault().register(this);
}

2 发布的是一个 Object 类,发送什么,订阅者就会接收什么

要注意两个地方:

  • 如果 post 字节数据,此时订阅者的方法里参数需为 Byte,而不是 byte,同样的,post int数据,订阅方法参数需为 Interger,其他基本数据类型同理
  • 如果 post 字符串,那么所有方法的参数是 String 类型的订阅者都会接收到事件而执行方法

3 遇到了订阅事件无法执行的情况

分析后发现是订阅事件的Activity 还未执行的原因,这时候就需要用到 postSticky。

发布事件时用 postSticky 操作:

EventBus.getDefault().postSticky(event);
//订阅时,添加 sticky = true

//看下 `@Subscribe` 源码知道 `sticky` 默认是 `false`
@Subscribe(sticky = true)
public void onEvent(Event e) {
    ---
}