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。

推荐阅读更多精彩内容

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