“Some Interesting Open Source Projects of Android”这个系列主要是对一些有意思的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
(2)使用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模式实现自由扩展项目配置。
当然,若项目无个性化配置需求,全部采用默认设置,则注册方法可以简化为:EventBus.getDefault().register(Object subscriber)。
EventBus采用Double Check Lock(DCL)模式实现单例,保证全局只有唯一EventBus实例。代码如下:
当用户注册EventBus后,会将所有订阅的方法订阅到EventBus中去。代码如下:
注意:若注册用户及其父类没有被@Subscribe注解的public方法,会抛出EventBusException方法。
2.1.2 订阅方法
在已注册的用户中,加入对应订阅方法。通过@Subscribe对方法进行注解(must have exactly 1 parameter, be public, non-static, and non-abstract),对方法名也取消了限制。
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);
可发送的事件可以为一切对象。但在项目中一般为方便管理,推荐使用新建类作为单一事件使用。
发送事件后处理代码如下:
对应线程模式下,唤起对应订阅者方法见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时应该开启索引加速。
三、小结
项目引入EventBus,最大作用之一是为了各模块解耦。通过灵活的线程模式,更大限度的满足各种复杂的业务需求。
在使用的过程中,需要注意因使用不当造成的额外开销造成资源浪费以及耗时索引等。下面对部分注意事项再次说明。
1)有注册(register)则必有注销(unregister)。
2)仅有@Subscribe注解订阅方法的类里,才能在EventBus注册当前用户(register)。
3)所有采用@Subscribe注解的订阅方法,必须是public,无返回值且只有一个参数(参数即为订阅的事件)。对方法名已取消命名限制。
4)合理使用ThreadMode。尤其避免在主线程进行耗时操作造成主线程阻塞。
本文对EventBus源码分析就到这了。在使用这些优秀的开源库的时候,花时间去弄清楚源码的设计理论与实现方式,不仅让我们对该开源库有更强大的掌控能力,而且会对我们编程思想有裨益。
有任何错误部分,烦请指正,谢谢~
# 附: