EventBus3(3.0.0)源码解析

144
作者 Burjal
2017.03.09 23:38* 字数 2655

Some Interesting Open Source Projects of Android”这个系列主要是对一些有意思的Android开源项目进行源码分析,错误之处烦请指正~


EventBus is a publish/subscribe event bus optimized for Android.


由于EventBus3.0.0(后文统一简写成“EventBus”)较以前版本无论是使用性能还是使用方式都有较大差异,本文分析基于最新的3.0.0版本。(如果您还是用的以前的版本,建议升级到最新版本吧,耳目一新的EventBus~)


一、EventBus 简介

伴随业务发展,app愈发臃肿,引发了一系列问题:

不同组件间的通信也变得复杂起来,Activity、Fragments以及后台线程间的通信造成app耦合度太高,非常不利于扩展。

代码复杂。

EventBus基于解耦的通信方式被越来越多的开发者青睐,除了解决上述问题之外,还解决了复杂的依赖性问题和各种生命周期问题,加之EventBus的轻量级(~50k jar),成为广为使用的组件间通信框架。

使用:

1.1 项目引入EventBus(Gradle)

打开对应模块的build.gradle,在dependencies中加入EventBus依赖:

compile 'org.greenrobot:eventbus:3.0.0'

EventBus3.0最大的优化就是通过EventBusAnnotationProcessor在编译时生成索引,提升索引速度。所以,肯定得引入到项目中啦~

可以使用两种方式通过编译时注解框架生成索引,详情可查看文档

(1)使用annotationProcessor


annotationProcessor

(2)使用android-apt


android-apt

此时,再编译一次,就会在项目中生成索引类。这样就可以在初始化EventBus的时候应用生成的索引文件了。在项目初始化的时候,将生成的 *EventBusIndex 通过EventBusBuilder配置到EventBus中,一般在Application的onCreate方法中执行该方法。

若项目开启混淆,则需要在proguard中加入如下代码:

-keepattributes*Annotation*

-keepclassmembersclass**{

@org.greenrobot.eventbus.Subscribe;

}

-keepenumorg.greenrobot.eventbus.ThreadMode{*;}

# Only required if you use AsyncExecutor

-keepclassmembersclass*extendsorg.greenrobot.eventbus.util.ThrowableFailureEvent{

(java.lang.Throwable);

}

1.2 成功接收到事件

订阅者需要将自身注册到bus上 {org.greenrobot.eventbus.EventBus #register(Object)}

一旦注册,订阅者在从bus上注销{org.greenrobot.eventbus.EventBus #unregister(Object)}之前,都能接收事件

1.3 注意事项

为避免反射带来的耗时,EventBus通过编译时注解,响应事件处理方法。事件处理方法有如下注意事项:

(1).方法必须加上{org.greenrobot.eventbus.Subscribe}注解

(2).方法必须为公共方法(public),无返回值(void)

(3).只有一个参数(发送的事件)


二、 源码解析

源码解析会按照使用流程分析源码,然后对项目里有意思的设计进行分析。

2.1 注册订阅,接收事件

用户(Activity,Fragment,View..)欲接收事件,需要实现接收对应事件的订阅方法并将当前用户注册到EventBus。

2.1.1 注册用户

EventBus.builder().build()..register(Object subscriber)

若项目中对EventBus需要进行私有化定制,可以通过EventBusBuilder类定制相关配置。EventBusBuilder通过Builder模式实现自由扩展项目配置。


EventBusBuilder 配置清单

当然,若项目无个性化配置需求,全部采用默认设置,则注册方法可以简化为:EventBus.getDefault().register(Object subscriber)。

EventBus采用Double Check Lock(DCL)模式实现单例,保证全局只有唯一EventBus实例。代码如下:


Double Check Lock(DCL) 单例模式

当用户注册EventBus后,会将所有订阅的方法订阅到EventBus中去。代码如下:


注册用户

注意:若注册用户及其父类没有被@Subscribe注解的public方法,会抛出EventBusException方法。

2.1.2 订阅方法

在已注册的用户中,加入对应订阅方法。通过@Subscribe对方法进行注解(must have exactly 1 parameter, be public, non-static, and non-abstract),对方法名也取消了限制。


@Subscribe

2.1.2.1 ThreadMode

ThreadMode提供四种线程模式:POSTING,MAIN,BACKGROUND,ASYNC,用以支持发送事件和处理接收事件线程独立。

POSTING:在发送线程上处理接收事件,以保证最小开销。使用过程中应避免在主线程发送事件以造成线程阻塞。

MAIN:在Android主线程(UI线程)处理接收事件。由于在主线程处理事件,所以在使用过程中应避免执行耗时操作以造成线程阻塞。

BACKGROUND:后台线程上处理接收事件,若在非主线程发送事件,则直接在当前线程处理接收。EventBus采用唯一的后台线程,确保所有发送事件按顺序交付。

ASYNC:单独线程上处理接收事件,与后台线程及主线程独立。EventBus通过线程池有效的限制线程数量,并高效复用已执行完毕异步任务。

不同线程模式下唤醒对应订阅者方法代码如下:


不同线程模式下唤醒对应订阅者方法

EventBus默认采用POSTING线程模式。

2.1.2.2 boolean sticky

sticky若为true,当订阅者处于活跃状态时,会交付最近的黏滞事件。在用户注册的时候,会先尝试唤醒执行一次订阅的方法(已存储的黏滞事件对象中有当前订阅者方法,详见2.2.2)。

EventBus默认sticky为false。

2.1.2.3 int priority

通过priority指定订阅者优先级以实现控制事件交付顺序。

priority仅支持在相同ThreadMode下,高优先级订阅者会更早收到事件(被唤醒)。

EventBus默认所有订阅者的priority均为0。

2.1.3 注销已注册用户

EventBus.getDefault().unregister(this);

为节省开销,养成良好的代码习惯,需要在所有注册(register)订阅EventBus的类里在合适的时机注销(unregister)订阅。Activity及Fragment一般在对应生命周期函数内采用对应方法,View中推荐在onAttachedToWindow()和onDetachedFromWindow()对应使用注册及注销方法(具体实现结合业务实现调整)。

注销用户代码如下:


注销已注册用户

注销当前用户订阅事件代码如下:


注销当前用户订阅事件

2.1.4 小结

为保证程序开销,切记在合适的时机对用户进行注册与注销。结合业务,合理选择线程模式以及优先级。

至此,用户订阅流程全部完成。

2.2 发送事件

在业务逻辑中,将对应事件发送到EventBus,EventBus通过唤起对应可用的订阅方法,完成全部模块业务逻辑。

2.2.1 发送普通事件

EventBus.getDefault().post(Object event);

可发送的事件可以为一切对象。但在项目中一般为方便管理,推荐使用新建类作为单一事件使用。

发送事件后处理代码如下:


post single event

对应线程模式下,唤起对应订阅者方法见2.1.2.1前图《不同线程模式下唤醒对应订阅者方法》。

2.2.2 发送黏滞事件

EventBus.getDefault().postSticky(Object event);

同发送普通事件,可发送的事件可以为一切对象。但在项目中一般为方便管理,推荐使用新建类作为单一事件使用。

发送黏滞事件代码如下:


发送黏滞事件

存储所有黏滞事件的stickyEvents会在所有用户注册的时候,一旦判断该用户有sticky=true的订阅者方法,就会唤起对应订阅者方法。

为节省开销,应在相关接收事件的模块及时回收掉当前发送的黏滞事件。

EventBus.getDefault().removeStickyEvent(Object event);

EventBus.getDefault().removeStickyEvent(Class eventType)

EventBus.getDefault().removeAllStickyEvents();//慎用

2.2.3 小结

发送普通事件后,EventBus唤醒对应可用的订阅者方法即完成一次处理流程。

发送黏滞事件,为节省开销,则需要在对应的时机回收掉该事件。

2.3 其他模块介绍

2.3.1 编译时注解框架(annotation processor)介绍

EventBus3.0中最大的升级之一是@Subscribe注解采用编译时注解框架实现代码插入。对应代码插入实现在EventBusAnnotationProcessor模块中。

在接入EventBus的模块中,可以自由选择是否通过注解生成订阅者方法索引列表,从而实现索引加速。

在引用订阅者索引加速后,编译时会将所有订阅方法存储在一个list中,生成文件位于"module/build/genereated/source/apt/..."目录下。

在EventBus接收到发送的事件后,会取出所有订阅该事件的方法。若开启索引加速后,则会直接从list中取出对应方法,否则通过反射查找对应方法,极大的影响性能。所以在EventBus3.0时应该开启索引加速。


SubscriberMethodFinder#findUsingInfo

四、小结

项目引入EventBus,最大作用之一是为了各模块解耦。通过灵活的线程模式,更大限度的满足各种复杂的业务需求。

在使用的过程中,需要注意因使用不当造成的额外开销造成资源浪费以及耗时索引等。下面对部分注意事项再次说明。

1)有注册(register)则必有注销(unregister)。

2)仅有@Subscribe注解订阅方法的类里,才能在EventBus注册当前用户(register)。

3)所有采用@Subscribe注解的订阅方法,必须是public,无返回值且只有一个参数(参数即为订阅的事件)。对方法名已取消命名限制。

4)合理使用ThreadMode。尤其避免在主线程进行耗时操作造成主线程阻塞。


本文对EventBus源码分析就到这了。在使用这些优秀的开源库的时候,花时间去弄清楚源码的设计理论与实现方式,不仅让我们对该开源库有更强大的掌控能力,而且会对我们编程思想有裨益。

有任何错误部分,烦请指正,谢谢~

# 附:

EventBus GitHub 项目地址

EventBus 官方文档地址

Android编译时注解框架系列