EventBus3(3.0.0)源码解析

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编译时注解框架系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 前言 在上一篇文章:EventBus 3.0初探: 入门使用及其使用 完全解析中,笔者为大家介绍了EventBus...
    丶蓝天白云梦阅读 15,824评论 21 128
  • 简介 我们知道,Android应用主要是由4大组件构成。当我们进行组件间通讯时,由于位于不同的组件,通信方式相对麻...
    Whyn阅读 531评论 0 1
  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,913评论 23 184
  • 原文链接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy阅读 542评论 1 5
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,473评论 0 50