项目地址:EventBus,本文分析版本: 3.1.1
一、概述
EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件。事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。
传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。
- 事件(Event):又可称为消息,本文中统一用事件表示。其实就是一个对象,可以是网络请求返回的字符串,也可以是某个开关状态等等。
事件类型(EventType)指事件所属的 Class。- 事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个 Sticky 事件(所谓「最近一个」指的就是该类型事件「最后一次发出」)。
- 订阅者(Subscriber):订阅某种事件类型的对象。当有发布者发布这类事件后,EventBus 会执行订阅者的 onEvent 函数,这个函数叫
事件响应函数。订阅者通过 register 接口订阅某个事件类型,unregister 接口退订。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。 - 发布者(Publisher):发布某事件的对象,通过 post 接口发布事件。
二、如何使用
2.1 添加依赖
方式一,运行期处理注解
在app 的 build.gradle 文件中添加依赖
|
|
方式二,编译期预处理注解
Android Studio 3.0 及以上
在 app 的 build.gradle 文件中添加
|
|
build 之后,会生成一个 MyEventBusIndex.java类。
然后在使用 EventBus 实例之前,又有两种方式可以将配置MyEventBusIndex.java配置到类中是哟经。
方式一 在构造 EventBus 时传入我们自定义的 EventBusIndex,
|
|
方式二 将索引应用到默认的单例中
使用 EventBus 之前,先调用下面的代码初始化 EventBus。
|
|
2.2 定义事件类
|
|
2.3 注册为监听者
在合适的地方(比如 Activity#onCreate、Fragment#onCreateView)通过下方代码进行注册
EventBus.getDefault().register(this);
2.4 编写响应事件的订阅方法
|
|
使用编译期注解处理的情况下,订阅方法的访问控制权限必须是 非 private 并且非 static 的
使用运行期反射处理的情况下,订阅方法的访问控制权限必须是 public 的
- 通过 ThreadMode 可以指定订阅方法在哪个线程执行,有四种选择
- ThreadMode.MAIN 事件订阅方法会在 UI 线程中执行
- 使用此模式的事件订阅方法必须快速返回以避免阻塞主线程。
- ThreadMode.POSTING (默认的模式)表示事件在哪个线程中发布出来的,事件订阅方法就会在这个线程中运行;
- 该模式避免了线程切换,适用于那些在很短的时间内完成的简单任务,无需主线程。使用这种模式的事件订阅方法必须快速返回以避免阻塞发布线程(发布线程可能是主线程)。
- ThreadMode.MAIN_ORDERED
- 在 Android 上,订阅方法将在 Android 的主线程中被调用。事件将会排队等待交付,这确保了 post 调用是非阻塞的。
- ThreadMode.BACKGROUND 子线程执行,如果本来就在子线程,直接在该子线程执行
- EventBus 使用一个后台线程,将按顺序发送所有事件。使用这种模式的订阅方法应该尽快返回以避免阻塞后台线程。
- 注意:「一个后台线程」所指的并不是
Executors.newSingleThreadPool(),而是使用 EventBus 在实例化时创建的 cacheThreadPool 中的某一个线程。
- 注意:「一个后台线程」所指的并不是
- EventBus 使用一个后台线程,将按顺序发送所有事件。使用这种模式的订阅方法应该尽快返回以避免阻塞后台线程。
- ThreadMode.ASYNC 新建子线程执行。适用于耗时操作
- 发布事件永远不会等待使用此模式的订阅方法。适用于比较耗时的订阅方法,比如用于网络请求。使用时应该避免同时触发大量长时间运行的异步订阅方法来限制并发线程的数量。 EventBus 使用线程池有效地重用已完成的异步用户通知中的线程。
- ThreadMode.MAIN 事件订阅方法会在 UI 线程中执行
- 通过 sticky 指定是否接收粘性事件,默认为 false
- 通过 priority 设置接收订阅方法的优先级,相同的事件,优先级越高的订阅方法 越早收到事件
2.5 发送事件
通过EventBus的post()方法来发送事件, 发送之后就会执行注册过这个事件的对应类的方法. 或者通过postSticky()来发送一个粘性事件。
2.6 解除注册
在合适的地方(比如 Activity#onDestroy)使用下面的代码进行解除注册 EventBus.getDefault().unregister(this);
2.7 小结
要实现订阅,需要进行注册,以及解注册,订阅方法以「目标事件」作为方法的参数, 使用 Subscribe 注解,可以指定订阅方法执行的线程、是否接收 sticky 事件、订阅方法的优先级。
至于发送方,只需要创建相应的 事件实例,然后调用 post 或者 postSticky 将事件发送出去即可。
三、实现
3.1 初始化 EventBus
开发者通常是调用 EventBus#getDefault 方法获取 EventBus 实例。
|
|
getDefault 通过双重校验锁的方式来实现单例
构造方法
|
|
通过 getDefault 获取的 EventBus 对象是通过默认的 EventBusBuilder 构造而成的。
|
|
主要看以下几个单例的实现。
|
|
- subscriptionsByEventType ,key 是事件类型,value 为 订阅了该事件的方法列表
- typesBySubscriber,key 为订阅者,value 某个订阅者订阅的事件列表
- stickyEvents,key 为事件类型,value 为具体的事件实例
- mainThreadPoster 主线程分发
- backgroundPoster 后台线程分发
- asyncPoster 异步线程分发
3.2 注册订阅
org.greenrobot.eventbus.EventBus#register
|
|
EventBus#findSubscriberMethods
找出给定 class 中所有的订阅方法
|
|
EventBus#subscribe()
|
|
该方法会将相应的事件插入对应事件的列表中。如果在方法注解中声明了 sticky,还会马上调用该方法。
检测 stick 事件,如果相应的事件定义有子类的话,会遍历事件的事件子类逐一通知该方法。
3.2.1 通过反射处理注解
SubscriberMethodFinder#findUsingReflection
|
|
SubscriberMethodFinder#findUsingReflectionInSingleClass
|
|
ignoreGeneratedIndex 是什么?
由于反射成本高,而且 EventBus 3.0 引入了 EventBusAnnotationProcessor,故默认 ignoreGeneratedIndex 为 false,需要注意的是,如果设置 ignoreGeneratedIndex 为 true,则前面使用的 MyEventBusIndex 无效,还是会走反射解析的分支。
3.2.2 使用编译期生成的订阅方法信息
网上有很多介绍 EventBus 的文章,但是几乎没有提到 EventBusAnnotationProcessor 的。在 3.0 版本开始,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe()注解并解析,处理其中所包含的信息,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快.我们可以参考EventBus项目里的EventBusPerformance这个例子,编译后我们可以在build文件夹里找到这个类,MyEventBusIndex 类,当然类名是可以自定义的.我们大致看一下生成的MyEventBusIndex类是什么样的:
订阅者中的 订阅方法
|
|
|
|
继续前面 ignoreGeneratedIndex 为 false 时,会执行以下分支。
|
|
findUsingInfo()方法,其无非就是通过查找我们前面所说的MyEventBusIndex类中的信息,来转换成List<SubscriberMethod>从而获得订阅类的相关订阅函数的各种信息
SubscriberMethodFinder#getSubscriberInfo
|
|
3.3 解注册
|
|
|
|
3.4 发送事件
EventBus#post
1. 获取订阅列表
|
|
|
|
EventBus#postSingleEvent
|
|
EventBus#postSingleEventForEventType,
|
|
2.切换到指定线程
|
|
BACKGROUND
|
|
MAIN
|
|
HandlerPoster#enqueue
|
|
org.greenrobot.eventbus.HandlerPoster#handleMessage
|
|
在 handleMessage 方法内部停留时间不能大于 10 毫秒,从
MAIN_ORDERED
新增加的模式,但是当前版本的实现还不完善。
ASYN
将事件添加 cachedThreadPool 中执行(如果当前有空闲线程,则复用空闲线程,如果没有就创建新线程)
|
|
3.反射调用订阅方法
org.greenrobot.eventbus.EventBus#invokeSubscriber(org.greenrobot.eventbus.PendingPost)
|
|
|
|
切换到指定线程之后,通过反射的方法,调用订阅方法。
四、总结
EventBus 的实现原理可以归结为以下三点
- 注册—>扫描订阅方法,添加到订阅方法列表中
- 发送事件—>根据事件的类型,遍历方法列表,反射调用订阅方法
- 解注册—>从订阅方法列表中移除相应的订阅方法
EventBus 虽然不是标准的观察者模式的实现, 但是它的整体就是一个发布 / 订阅框架, 也拥有观察者模式的优点, 比如: 发布者和订阅者的解耦。
五、参考资料与学习资源推荐
由于本人水平有限,可能出于误解或者笔误难免出错,如果发现有问题或者对文中内容存在疑问欢迎在下面评论区告诉我。谢谢!