图解EventBus源码

观察者模式和订阅发布模式预览

开局一张图,故事全靠编。(看不见水印,看不见水印,看不见水印)

1.EventBus源于订阅发布模式。

在订阅发布模式(上图右)中,发布者和订阅着互相不感知对方的存在,双方通过消息代理进行通信,各组件间松耦合。Eventbus的作用正如上图的EventChannel,它提供的功能更是一种总线机制,甚至可以说是路由机制。发布者将消息发布到总线Eventbus上,剩下的工作交有Eventbus来处理。订阅者被EventBus持有和维护,EventBus将消息一一发布给订阅者。正因此,我们项目中,一版可以显示找到订阅者入activity,但是发布者则隐藏于各种,随处都可以是发布者,随处都可以post出来消息。

而在观察者模式(上图左)中,观察者和被观察者没有完全解耦,抽象被观察者持有抽象观察者,并维护观察者列表。与订阅模式相当于在观察者和被观察者之间架了一层,由这个中间层来持有观察者,这样被观察者无须感知观察者。

订阅发布模式
观察者模式
EventBus核心结构

2.register过程

image5.png

通过findSubscriberMethods查找回来一个包含订阅者所有订阅方法的订阅列表。跟进findSubscriberMethods如何查找。

image6.png

通过运行时反射遍历查找

image7.png
image8.png

如果设置了索引加速,通过索引查找。原理是EventBusAnnotationProcessor。

image9.png

找打订阅方法后,开始进行一系列的存储操作

image10.png
image11.png

如果是黏性属性的方法,则立即去缓存黏性事件的stickyEvents查找是否有黏性事件,有则立即post到当前订阅者里执行,且只有当前订阅者会执行一次,其他地方不会执行,也没有查找订阅者这一过程。

register小结

register过程
核心数据结构

3.post过程

image14.png
image15.png
image16.png
image17.png

ThreaMode说明

PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法(哪个线程post出来就在哪个线程中执行),不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;

MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;

BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;

Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。

再来看看黏性事件的post过程

image18.png

注意看,这里有个加锁的过程,为什么postSticky需要加锁,而post不需要加锁?因为postSticky的事件会先存储到Map<Class<?>, Object> stickyEvents中,而stickyEvents是EventBus全局只有一个

,也就是所有的黏性事件都存储在这个map中,故当不同线程同时poststicky事件的时候存在并发问题。而普通的post出来的事件,都是存储在线程的本地变量的事件队列里,各线程互不干扰互相不能访问对方的数据,故不存在并发问题。

post小结

post过程
image20.png

ThreadLocal:线程用来存储私有变量(一个很有意思的东西)

名义上以ThreadLocalMap变量形式在线程内部,但是底层实现是基于Entry[]数组而不是HashMap。(这里引发一个思考,为什么要用数组实现,其实数组也可以称一种map,用数组实现开放定址法处理冲突,

用数组存储key和value更节省内存,普通的HashMap是拉链法解决冲突,基于数组和链表,每个Entry元素除了key和value还要一个Entry类型的next指针,占用更多内存。同样的思想也在Android的sparseArray和ArrayMap中使用。)

Entry{

Object key; // key实际上是我们定义的ThreadLocal对象,而非当前线程对象

Object value;

}

image21.png

4.unregister

image22.png
image23.png
image24.png

5思考&总结

优点:

1.事件总线通信,使用简单

2.解耦,干脆利落

缺点:

1.极致的解耦导致项目维护和阅读难度增大,出现EventBus满天飞的场景

2.更甚者,它使我们往往懒于去代码中找寻设计的快感

3.如果不及时unregister则会内存泄露

关于性能:

1.EventBus3.0以前大量使用反射,存在性能瓶颈,3.0以后引入APT(注解处理器)后在编译期解析注解,性能飙升

2.EventBus在每次查找到订阅者和其绑定的订阅方法集合后会放在Map里缓存。(这是注解框架的一贯套路,Butterknife亦如是)

参考文章:
http://www.cnblogs.com/bugly/p/5475034.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容