EventBus 是一个基于观察者模式的事件订阅/发布框架,利用 EventBus 能够在不一样模块之间,实现低耦合的消息通讯。java
EventBus 由于其使用简单且稳定,被普遍应用在一些生产项目中。面试
一般咱们就是使用 EventBus 分发一些消息给消息的订阅者,除此以外咱们还能够经过 EventBus 将消息传递到不一样的线程中去执行,处理消息。这其中还涉及到一些线程切换问题、线程池的问题,在使用的过程当中,还有一些配置的选择,此时咱们须要根据不一样的业务场景,来选择不一样的线程切换方式。安全
本文就 EventBus 的几种线程切换方式,以及内部的实现原来,来分析如何使用 EventBus 来切换消息线程。框架
EventBus 是一个基于观察者模式的事件订阅/发布框架。利用 EventBus 能够在不一样模块之间,实现低耦合的消息通讯。async
EventBus 诞生以来这么多年,在不少生产项目中均可以看到它的身影。而从更新日志能够看到,除了体积小,它还很稳定,这两年就没更新过,最后一次更新也只是由于支持全部的 JVM,让其使用范围不只仅局限在 Android 上。ide
可谓是很是的稳定,稳定到让人有一种感受,要是你使用 EventBus 出现了什么问题,那必定是你使用的方式不对。oop
EventBus 的使用方式,对于 Android 老司机来讲,必然是不陌生的,相关资料太多,这里就再也不赘述了。post
在 Android 下,线程的切换是一个很经常使用并且很必须的操做,EventBus 除了能够订阅和发送消息以外,它还能够指定接受消息处理消息的线程。学习
也就是说,不管你 post()
消息时处在什么线程中,EventBus 均可以将消息分发到你指定的线程上去,听上去就感受很是的方便。优化
不过不管怎么切换,无外乎几种状况:
在咱们使用 EventBus 注册消息的时候,能够经过 @Subscribe
注解来完成注册事件, @Subscribe
中能够经过参数 threadMode
来指定使用那个线程来接收消息。
@Subscribe(threadMode = ThreadMode.MAIN) fun onEventTest(event:TestEvent){ // 处理事件 }
threadMode
是一个 enum,有多种模式可供选择:
EventBus 的线程切换,主要涉及的方法就是 EventBus 的 postToSubscription()
方法。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
能够看到,在 postToSubscription()
方法中,对咱们配置的 threadMode 值进行了处理。
这端代码逻辑很是的简单,接下来咱们看看它们执行的细节。
想在主线程接收消息,须要配置 threadMode 为 MAIN。
case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); }
这一段的逻辑很清晰,判断是主线程就直接处理事件,若是是非主线程,就是用 mainThreadPoster 处理事件。
追踪 mainThreadPoster
的代码,具体的逻辑代码都在 HandlerPoster 类中,它实现了 Poster 接口,这就是一个普通的 Handler,只是它的 Looper 使用的是主线程的 「Main Looper」,能够将消息分发到主线程中。
为了提升效率,EventBus 在这里还作了一些小优化,值得咱们借鉴学习。
为了不频繁的向主线程 sendMessage()
,EventBus 的作法是在一个消息里尽量多的处理更多的消息事件,因此使用了 while 循环,持续从消息队列 queue 中获取消息。
同时为了不长期占有主线程,间隔 10ms (maxMillisInsideHandleMessage = 10ms)会从新发送 sendMessage()
,用于让出主线程的执行权,避免形成 UI 卡顿和 ANR。
MAIN
能够确保事件的接收,在主线程中,须要注意的是,若是事件就是在主线程中发送的,则使用 MAIN
会直接执行。为了让开发和可配置的成都更高,在 EventBus v3.1.1 新增了 MAIN_ORDERED
,它不会区分当前线程,而是统统使用 mainThreadPoster
来处理,也就是必然会走一遍 Handler 的消息分发。
当事件须要在主线程中处理的时候,要求不能执行耗时操做,这没什么好说的,另外对于 MAIN
或者 MAIN_ORDERED
的选择,就看具体的业务要求了。
想要让消息在子线程中处理,能够配置 threadMode 为 BACKGROUND
或者 AYSNC
,他们均可以实现,可是也有一些区别。
先来看看 BACKGROUND
,经过 postToSubscription()
中的逻辑能够看到,BACKGROUND
会区分当前发生事件的线程,是不是主线程,非主线程这直接分发事件,若是是主线程,则 backgroundPoster
来分发事件。
case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break;
BackgroundPoster 也实现了 Poster 接口,其中也维护了一个用链表实现的消息队列 PendingPostQueue,
在一些编码规范里就提到,不要直接建立线程,而是须要使用线程池。EventBus 也遵循这个规范,在 BackgroundPoster 中,就使用了 EventBus 的 executorService
线程池对象去执行。
为了提升效率,EventBus 在处理 BackgroundPoster 时,也有一些小技巧值得咱们学习。
能够看到,在 BackgroundPoster 中,处理主线程抛出的事件时,同一时刻只会存在一个线程,去循环从队列中,获取事件处理事件。
经过 synchronized 同步锁来保证队列数据的线程安全,同时利用 volatile 标识的 executorRunning
来保证不一样线程下看到的执行状态是可见的。
既然 BACKGROUND
在处理任务的时候,只会使用一个线程,可是 EventBus 却用到了线程池,看似有点浪费。可是再继续了解 ASYNC
的实现,才知道怎么样是对线程池的充分利用。
和前面介绍的 threadMode 同样,大多数都对应了一个 Poster,而 ASYNC
对应的 Poster 是 AsyncPoster,其中并无作任何特殊的处理,全部的事件,都是无脑的抛给 EventBus 的 executorService
这个线程池去处理,这也就保证了,不管如何发生事件的线程,和接收事件的线程,必然是不一样的,也保证了必定会在子线程中处理事件。
public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); }
到这里应该就理解了 BACKGROUND
和 ASYNC
,虽然均可以保证在子线程中接收处理事件,可是内部实现是不一样的。
BACKGROUND
同一时间,只会利用一个子线程,来循环从事件队列中获取事件并进行处理,也就是前面的事件的执行效率,会影响后续事件的执行。例如你分发了一个事件,使用的是 BACKGROUND
可是队列前面还有一个耗时操做,那你分发的这个事件,也必须等待队列前面的事件都处理完成才能够继续执行。因此若是你追求执行的效率,马上立刻就要执行的事件,可使用 ASYNC
。
那是否是都用 ASYNC
就行了?固然这种一揽子的决定都不会好,具体问题具体分析,ASYNC
也有它本身的问题。
ASYNC
会无脑的向线程池 executorService 发送任务,而这个线程池,若是你不配置的话,默认状况下使用的是 Executors 的 newCachedThreadPool()
建立的。
这里我又要说到编码规范了,不推荐使用 Executors 直接建立线程,之因此这样,其中一个缘由在于线程池对任务的拒绝策略。 newCachedThreadPool
则会建立一个无界队列,来存放线程池暂时没法处理的任务,说到无界队列,拍脑壳就能想到,当任务(事件)过多时,会出现的 OOM。
这也确实是 EventBus 在使用 ASYNC
时,真实存在的问题。
可是其实这里让开发者本身去配置,也很难配置一个合理的线程池的拒绝策略,拒绝时必然会放弃一些任务,也就是会放弃掉一些事件,任何放弃策略都是不合适的,这在 EventBus 的使用中,表现出来就是出现逻辑错误,该收到的事件,收不到了。因此你看,这里无界队列不合适,可是不用它呢也不合适,惟一的办法就是合理的使用 ASYNC
,只在必要且合理的状况下,才去使用它。
到这里基本上 EventBus 在分发事件时的线程切换,就讲清除了,不少资料里其实都写了他们能够切换线程,可是对于一些使用的细节,描述的并不清除,正好借此文,把 EventBus 的线程切换的直接讲清除。
EventBus 也是简历上比较常见的高频词,我在面试的过程当中,也常常会问面试者,关于它是如何作到线程切换的问题。可是正由于它简单易用,其实不少时候咱们都忽略了它的实现细节。
今天就到这里,小结一下:
1. EventBus 能够经过 threadMode 来配置接收事件的线程。
2. MAIN 和 MAIN_ORDERED 都会在主线程接收事件,区别在因而否区分,发生事件的线程是不是主线程。
3. BACKGROUND 确保在子线程中接收线程,它会经过线程池,使用一个线程循环处理全部的事件。因此事件的执行时机,会受到事件队列前面的事件处理效率的影响。
4. ASYNC 确保在子线程中接收事件,区别于 BACKGROUND,ASYNC 会每次向线程池中发送任务,经过线程池的调度去执行。可是由于线程池采用的是无界队列,会致使 ASYNC 待处理的事件太多时,会致使 OOM。
本文就到这里,本文对你有帮助吗?留言、转发、收藏是最大的支持,谢谢!
本文首发自公众号「承香墨影」,欢迎关注获取最新的原创文章。公众号后台回复成长『 成长』,将会获得我准备的学习资料,。