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

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

从上面的类图可以看出,都是围绕类EventBus 通过组合来建立关联。
可以看到优秀的框架设计,多用组合,少用继承。
源码分析
EventBus.getDefault();
获取Event Bus的实例:

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

在构造函数里进行了一系列的初始化工作,主要通过类EventBusBuilder 使用Build的模式来初始化EventBus的一些配置。
还有几个重要的数据结构集合的初始化:
subscriptionsByEventType
订阅该事件的所有订阅者 。是一个以 key:订阅的事件 value:订阅这个事件的所有订阅者。因此当post的时候会遍历这个Map相应对应事件的List。
typesBySubscriber
订阅者中的所有订阅事件 。是一个以key:订阅者 value:订阅者中的所有订阅事件。用于后续的取消订阅。
三个Poster类

Poster类是用来处理sticky事件。
register(this)

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

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

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

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

在112中 可以看到 会从对象池FIND_STATE_POOL
取出FindState 而不是每次都创建对象。
回到 findUsingReflection
方法当查找完所有的订阅事件方法,调用getMethodsAndRelease
对FindState进行回收复用:

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

- 在154行 通过反射 获取订阅者(this)的所有方法。
- 在160 行遍历这些方法。
- 在164行 判断是否带@Subscribe 注解的方法 是否只有一个参数,如果否 就会抛出异常
- 在165行判断方法是否带@Subscribe 注解 ,如果是,通过FindState对象进行校验和保存。
其实很简单 就是通过反射来查找到订阅者的所有方法,查找出带@Subscribe注解的方法保存起来。
回到register
方法,查找完所有的订阅者中的所有订阅事件方法,之后遍历列表,进入subscribe
方法:

- 在146行 获取订阅方法的订阅类型(就是带@Subscribe 注解的方法的第一个参数)。
- 封装
Subscription
对象,保存到subscriptionsByEventType
数据集合中。 - 在159行,根据优先级 把封装
Subscription
对象,保存到typesBySubscriber
数据集合中。 - 在174行 如果是
sticky
事件,立即post sticky
事件到当前订阅者。
整个register的流程大概就是这样:

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

首先从currentPostingThreadState 获取 PostingThreadState 对象
currentPostingThreadState 是个ThreadLocal对象

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

- 在365行,判断是否触发订阅该事件的父类,接口的方法。
- 调用postSingleEventForEventType 方法分发事件。
进入postSingleEventForEventType方法

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

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

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

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

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