深入分析Android EventBus组件

目录

  1. Simple Demo
  2. 为什么使用
  3. 原理分析
  4. 关于EventBus思考
  5. 参考

这里要讲的EventBus特指greenrobot开源的项目(https://github.com/greenrobot/EventBus),是一个Android事件发布/订阅框架,通过解耦发布者和订阅者来简化Android事件传递。事件传递既可以用于Android四大组件间通信,也可以应用于异步线程和主线程间通信等。

Simple Demo

1. 定义事件

1
2
3
4
5
6
7
8
9
public class DemoEvent {
public String name;
public int grade;

public DemoEvent(String name, int grade) {
this.name = name;
this.grade = grade;
}
}

2. 订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EventBusDemoActivity extends AppCompatActivity {
...
// register
EventBus.getDefault().register(this);

// proceed event
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1, sticky = true)
public void onDemoCallBack(DemoEvent event) {
// do something...
}

@Subscribe
public void onDemoCallBackNew(DemoEvent event) {
// do something...
}

// unregister
EventBus.getDefault().unregister(this);
...
}

3. 发布者

1
EventBus.getDefault().post(new DemoEvent("Hello World", 10));

4. Subscriber Index

EventBus3.0使用注解标明订阅者的回调方法,如果运行时使用反射解析注解会带来效率问题,于是EventBus3.0提供了编译时就把包含订阅者的回调方法等相关信息类构建出来,提高运行效率,所以叫Subscriber Index,但是要求subscriber和event的class都是public。当然,EventBus默认如果Subscriber Index没有找到时,则会再次使用反射。

  • 构建脚本修改
    为了在android studio实现annotation processors,需要android-apt plugins(https://bitbucket.org/hvisser/android-apt),最终gradle脚本如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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.0'
    }
    apt {
    arguments {
    eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
    }
  • 设置EventBus

    1
    2
    3
    4
    5
    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();

为什么使用

  • vs. 观察者模式
    观察者模式很大的问题是实现observable的观察者要把自身注册到被观察者类中,这意味着两者之间属于强关联关系,使得代码耦合性太高;随着应用功能增加,需要监听的事件越来越多,而越来越多的控件需要监听不同的事件,则导致越来越多的控件需要注册到各种事件管理器,注册和取消这些事件会变得越来越难以管理,测试越来越苦难,容易引入各种奇怪的Bug。
  • vs. LocalBroadcastManager
    本地广播的API不如EventBus简洁,此外,Intent要注意保持extra类型一致性,因为编译期间并不能检查出发送的extra和收到类型的一致性,容易导致的错误是团队中其他人改变了传递数据,但忘记对全部的receiver进行更新,而且只有在运行时才暴露问题。
  • EventBus优点
    • 简化组件间通信,解耦合
    • 简化代码,增强了类型安全
    • fast/tiny( < 50k)
    • 相比Otto提供了多种事件响应的线程模式、订阅者优先级和sticky event等一些高级特性
    • 大约100,000,000+ app使用

原理分析

因为EventBus2.4版源码已经有好多人进行分析,而3.0版本改变并不大,主要是标识订阅者的回调函数的方式不同,我仅从整体流程方面进行梳理一下,具体实现见源码。

  • 2.4 v.s. 3.0

    • 2.4
      2.4版本遵循“约定优于配置”的原则,事件处理方法命名必须以”onEvent”开始,后面可以跟四种表示以何种方式去通知订阅者的名称,即约定事件处理方法名有以下四种:

      • onEvent : 默认在post event线程中回调
      • onEventMainThread : UI线程回调
      • onEventBackgroundThread : 默认情况,每个EventBus实例有一个单独的后台线程去执行回调
      • onEventAsync : 默认情况,每个EventBus实例有一个newCachedThreadPool的线程池去执行Aysnc;上述BackgroundThread也属于该线程池管理
    • 3.0
      3.0版本使用注解进行标识,具体定义如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.METHOD})
      public @interface Subscribe {
      ThreadMode threadMode() default ThreadMode.POSTING;

      /**
      * If true, delivers the most recent sticky event (posted with
      * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
      */
      boolean sticky() default false;

      /** Subscriber priority to influence the order of event delivery.
      * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
      * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
      * delivery among subscribers with different {@link ThreadMode}s! */
      int priority() default 0;
      }

EventBus类提供默认的单例实现,并可以通过Builder模式定制EventBus实例。通过Demo我们知道关键过程就是register和post,我们分别从源码级别看一下实现。

  • register

订阅者register调用方法原型如下

1
public void register(Object subscriber)

该函数首先根据订阅者类的信息,得到包含SubscriberMethod对象的队列,其过程如下图所示:

针对上述队列中每个SubscriberMethod对象,调用如下函数

1
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod)

每个EventBus实例有一个Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType,其中key值是订阅者的事件类型,value是一个队列,里面存放着订阅该事件的订阅者信息,信息被封装成一个Subscription类。相同事件的不同订阅者会根据priority放在队列里合适的位置上。同时,EventBus实例还有一个Map<Object, List<Class<?>>> typesBySubscriber,保存着每个订阅者所订阅的事件,用于unregister时根据订阅者查找出所订阅的事件,然后从该事件队列中把订阅者remove。如下图所示:

到此订阅者register过程结束,但最后有个sticky特性需要说一下。EventBus持有一个Map<Class<?>, Object> stickyEvents,里面保存sticky event;发布事件时可以调用postSticky方法,该事件会保存在Map中。订阅者register方法最后检查该订阅者回调函数中的订阅事件是否是sticky,如果sticky=true且存在stickyEvents中,则说明该事件已经被发布,所以会立刻通知订阅者该事件。如下图所示:

  • post

EventBus实例提供2个post函数,其原型如下

1
2
public void post(Object event) 
public void postSticky(Object event)

其中postSticky函数首先把event放入stickyEvents的Map,然后调用post函数。我们主要分析post函数。
每个EventBus实例存有一个ThreadLocal变量currentPostingThreadState,其维护的变量结构如下,eventQueue用于存放事件Event的队列;isPosting标识是否开始循环处理eventQueue队列中的事件;isMainThread标识发布事件的线程是否是主线程;subscription和event主要是用于循环时保留事件和对应的Subscription,通过反射通知订阅者;canceled用于取消发布事件。整个post过程如下图所示。

关于EventBus思考

  • ProGuard 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -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
    EventBus的utils类中提供了一个工具类AsyncExecutor,可以通过它执行一个扩展的Runnable,内部可以捕获Throwable,然后回调注册参数为ThrowableFailureEvent的onEvent方法,可以全局监测Runnable执行异常的捕获处理。相关代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    AsyncExecutor.create().execute(
    new RunnableEx {
    public void run throws LoginException {
    remote.login();
    EventBus.getDefault().postSticky(new LoggedInEvent());
    // No need to catch Exception
    }
    }
    }

    public void onEventMainThread(LoggedInEvent event) {
    // Change some UI
    }

    public void onEventMainThread(ThrowableFailureEvent event) {
    // Show error in UI
    }

参考

  1. http://greenrobot.org/eventbus/
  2. http://a.codekk.com/detail/Android/Trinea/EventBus%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
  3. http://www.ithao123.cn/content-7461338.html
  4. http://android.jobbole.com/81098/