前两天去面试,问起EventBus,发觉自己表达还是很糟糕啊~
EventBus是什么?应用场景?
方便组件通讯,降低耦合
例子:
问题需求:
QQ查找附近好友列表页面
,跟这些用户关系,好友或非好友,查看某个非好友用户详情页面
,并在详情页面添加为好友,
返回到列表页面时,与他的关系没有更新;
传统解决方案:
1 | //1. 定义接口 |
其中麻烦的有两点:繁琐的创建额外一个接口;获取到接口实现的引用
当然,也可以不实现接口,详情页面直接获取到列表页面的引用,但这就不符合设计原则了,详情页面不该拥有对列表页面的全部控制权。
EventBus解决方案
- 列表页面作为订阅者,订阅修改状态的事件
- 详情页面作为发布者,发布状态更新的事件
这样,代码简单,耦合度也低了。
当然,评判好坏,偶合低的另一极端是,容易导致滥用,在多处地方发布时间,管理不当,可能会导致总线拥堵。
EventBus的实现原理?
注册订阅者时,遍历该订阅者类中标记@Subscriber
的方法,明确这个订阅者订阅了什么事件,以及针对该事件该怎么处理,即将订阅者的信息
注册到EventBus实例中。
发布者发送事件时,查询EventBus中订阅有该事件的订阅者,调用相应订阅方法
其中,如何获取到订阅者信息呢?有两种方式。
- 运行时,通过反射获取订阅者信息
- 编译时,通过注解处理器生成订阅者信息的索引
对比两者优劣,前者简单,后者稍有麻烦,通过减少注册时消耗的时间,提升性能
注意,无论使用选择哪种方式,都要注意混淆时保留订阅者的处理事件方法的信息
1 | -keepattributes *Annotation* |
编译时生成订阅者索引的具体操作方法:
使用kotlin如下,java环境参考官网文档
1 |
|
ThreadMode 列举描述?
threadMode
线程模式描述了处理事件应该在哪个线程
POSTING
与发送事件的线程相同MAIN
始终在主线程MAIN_ORDERED
基于MAIN,并保证事件会同步有序处理BACKGROUND
与MAIN相反,是在不会在主线ASYNC
始终不会在发送事件的线程,发送事件事件方法调用会立刻返回
具体解析:
POSTING
是默认的,这是为了性能考虑。当发送事件的线程和处理事件线程相同时,不会引起线程切换,而是直接的方法调用
直接方法调用
存在一个问题:
发送事件的方法要等到处理事件的方法返回才能返回,假设处理方法耗时很久,发送事件方法也要一直等。
在MAIN
模式中会有一个问题:
假设使用了MAIN模式来处理事件,线程t1
, t2
先后分别发送了e1
,e2
, 主线程会先处理e1,若是处理e1未结束,e2事件到来,
那么主线程会交错处理t1
,t2
,可能不会是我们预想的结果,先处理完t1
,再开始处理t2
开始没有想通,故写了demo来验证,可参考
故可以使用MAIN_ORDERED
来实现这个需求,内部通过一个维护一个pendingPostPool
等待发送的事件列表,这样,才能够等t1处理完,在开始处理
t2.
Async
很强大保证每个发送的事件都会尽快处理,因为内部默认使用了可缓存的线程池CacheThreadPool
来处理多个任务,避免多个事件到来时需要
等待处理。
newCacheThreadPool
描述:
作为一个线程池,当没有任务到来时不会创建有线程,
有任务到来会创建线程来处理,处理结束后,不会直接销毁,而是保留一段时间(默认60s),存活期间可处理继续处理任务,避免了创建线程影响开销。
相应的,除了上面提到的线程池,java默认还提供了3中线程池:
- newFixedThreadPool 默认就创建了几个核心的线程,核心线程不会因为任务结束而销毁,且也会限制最大的线程个数
- newScheduledThreadPool 定时或周期性执行任务
- newSingleThreadExecutor 使用单线程来处理任务,未处理的线程会进入等待队列