开局一张图,故事全靠编。(看不见水印,看不见水印,看不见水印)
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.pngThreaMode说明
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.pngThreadLocal:线程用来存储私有变量(一个很有意思的东西)
名义上以ThreadLocalMap变量形式在线程内部,但是底层实现是基于Entry[]数组而不是HashMap。(这里引发一个思考,为什么要用数组实现,其实数组也可以称一种map,用数组实现开放定址法处理冲突,
用数组存储key和value更节省内存,普通的HashMap是拉链法解决冲突,基于数组和链表,每个Entry元素除了key和value还要一个Entry类型的next指针,占用更多内存。同样的思想也在Android的sparseArray和ArrayMap中使用。)
Entry{
Object key; // key实际上是我们定义的ThreadLocal对象,而非当前线程对象
Object value;
}
image21.png4.unregister
image22.png image23.png image24.png5思考&总结
优点:
1.事件总线通信,使用简单
2.解耦,干脆利落
…
缺点:
1.极致的解耦导致项目维护和阅读难度增大,出现EventBus满天飞的场景
2.更甚者,它使我们往往懒于去代码中找寻设计的快感
3.如果不及时unregister则会内存泄露
…
关于性能:
1.EventBus3.0以前大量使用反射,存在性能瓶颈,3.0以后引入APT(注解处理器)后在编译期解析注解,性能飙升
2.EventBus在每次查找到订阅者和其绑定的订阅方法集合后会放在Map里缓存。(这是注解框架的一贯套路,Butterknife亦如是)
网友评论