EventBus 3.0 源码分析

功能

EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递。事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。

使用

01.png

这是GitHub,EventBus的使用介绍,用起来还是很简单的。

类图

02.png

从上面的类图可以看出,都是围绕类EventBus 通过组合来建立关联。
可以看到优秀的框架设计,多用组合,少用继承。

源码分析

EventBus.getDefault();

获取Event Bus的实例:

03.png

使用单例模式来创建出Event Bus对象,采用了双重验证 以保证线程安全。,看看Event Bus的构造函数。

04.png

在构造函数里进行了一系列的初始化工作,主要通过类EventBusBuilder 使用Build的模式来初始化EventBus的一些配置。

还有几个重要的数据结构集合的初始化:

subscriptionsByEventType

订阅该事件的所有订阅者 。是一个以 key:订阅的事件 value:订阅这个事件的所有订阅者。因此当post的时候会遍历这个Map相应对应事件的List。

typesBySubscriber

订阅者中的所有订阅事件 。是一个以key:订阅者 value:订阅者中的所有订阅事件。用于后续的取消订阅。

三个Poster类

05.png

Poster类是用来处理sticky事件。

register(this)

06.png

委托SubscriberMethodFinder类,查找出订阅者中的所有订阅事件方法。进入findSubscriberMethods方法看看:

07.png

该方法的处理流程大概是这样的:

09.png

这里我们只分析 通过findUsingReflection 方法,通过反射获取订阅者中的所有订阅事件方法。进去看看这个方法:

11.png

查找到的订阅事件会在临时的类FindState中做校验和保存,为什么会提到这个临时类呢?这里有一个很好的设计,使用了“对象池”的概念来创建FindState对象,和线程池同一个概念, 这样可以使对象FindState复用,防止创建过多的对象,增加内存开销。

去看看如何复用FindState对象的,进入上图prepareFindState方法:

12.png

在112中 可以看到 会从对象池FIND_STATE_POOL取出FindState 而不是每次都创建对象。

回到 findUsingReflection方法当查找完所有的订阅事件方法,调用getMethodsAndRelease对FindState进行回收复用:

13.png

还是回到 findUsingReflection 方法,调用了findUsingReflectionInSingleClass 方法来进行查找订阅事件方法,进去看看是怎么查找的:

14.png
  1. 在154行 通过反射 获取订阅者(this)的所有方法。
  2. 在160 行遍历这些方法。
  3. 在164行 判断是否带@Subscribe 注解的方法 是否只有一个参数,如果否 就会抛出异常
  4. 在165行判断方法是否带@Subscribe 注解 ,如果是,通过FindState对象进行校验和保存。

其实很简单 就是通过反射来查找到订阅者的所有方法,查找出带@Subscribe注解的方法保存起来。

回到register方法,查找完所有的订阅者中的所有订阅事件方法,之后遍历列表,进入subscribe方法:

16.png
  1. 在146行 获取订阅方法的订阅类型(就是带@Subscribe 注解的方法的第一个参数)。
  2. 封装Subscription对象,保存到subscriptionsByEventType数据集合中。
  3. 在159行,根据优先级 把封装Subscription对象,保存到typesBySubscriber数据集合中。
  4. 在174行 如果是sticky事件,立即post sticky事件到当前订阅者。

整个register的流程大概就是这样:

17.png

post 事件

最后通过post方法 来发送事件,进入post方法

18.png

首先从currentPostingThreadState 获取 PostingThreadState 对象

currentPostingThreadState 是个ThreadLocal对象

19.png

ThreadLocal使得各线程能够保持各自独立的一个对象。
把要post的事件加入PostingThreadState 的eventQueue队列中,循环取出事件。
进入postSingleEvent方法

20.png
  1. 在365行,判断是否触发订阅该事件的父类,接口的方法。
  2. 调用postSingleEventForEventType 方法分发事件。

进入postSingleEventForEventType方法

21.png
  1. 在389行,从subscriptionsByEventType数据集合中取出该订阅事件的所有订阅者。
  2. 在392行,分发所有的订阅者,通过调用postToSubscription方法。

进入postToSubscription方法

22.png

根据不同的threadMode 在不同的线程中处理,最终都会调用invokeSubscriber方法,把事件invoke到订阅者的方法中。

23.png

ThreadMode 共有四类:

  1. PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作
  2. MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread
    类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作
  3. BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
  4. Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问

引用出自

unregister

25.png

从 typesBySubscriber 数据集合中取出该订阅者的所有订阅方法,remove所有订阅者的订阅方法,防止内存泄漏。

整个post流程:

24.png

最后

EventBus 属于一个比较容易理解的开源库,项目整体的框架设计采用了观察者模式,但不论从使用方式和实现方式上都是非常值得我们学习的开源项目。比如,对象池的设计,三大Poster类的设计,多用组合,少用继承,缓存的设计等都值得我们借鉴和使用。

END。

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

推荐阅读更多精彩内容

  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,900评论 23 184
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,467评论 0 50
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,397评论 25 707
  • 我觉得自己真的活的有点累了,有点不知道该怎么继续去努力。 从小我都是一个乖到不行的小姑娘。 小学的时候,大家都一样...
    yangming14阅读 275评论 0 0
  • 做了一些404页面练习,根据网上一些资料以及结合自己的理解,来总结一下关于404有页面的一些东西。 404页面是什...
    青木睡不着阅读 519评论 0 1