EventBus是一个机遇观察者模式的时间订阅/发布框架,利用EventBus可以在不同模块之间,实现低耦合的消息通信。EventBus使用简单且稳定,被广泛用在一些生产项目中。
通常我们使用EventBus分发一些消息给消息的订阅者,除此之外我们还可以通过EventBus将消息传递到不同的线程中去执行,处理消息。
本文结合源码分析EventBus切换线程的原理。
在Android中,线程的切换是一个很常用而且很必须的操作,EventBus除了可以订阅和发送消息,还可以指定接收消息处理的线程。
线程切换,大概有一些几种情况:
在使用EventBus订阅消息的时候,使用@Subscribe来订阅消息,@Subscribe 中可以通过参数 threadMode 来指定使用那个线程来接收消息。
@Subscribe(sticky = true,threadMode = ThreadMode.MAIN)
public void onTestEvent(TestEvent event) {
if(event!=null){
//处理消息
}
}
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);
}
}
代码比较简单,下面分别讲解如何切换到主线程和如何切换到子线程中执行。
想在主线程接收消息,需要配置threadMode为MAIN或者MAIN_ORDERED。
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;
这里先分析下threadMode为MAIN的情况,判断是主线程就直接处理事件,如果是非主线程,则通过mainThreadPoster来处理事件。
追踪mainThreadPoster发现:
EventBus(EventBusBuilder builder) {
...
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
...
}
mainThreadPoster通过mainThreadSupport的createPoster方法创建。
EventBusBuilder的getMainThreadSupport()方法如下:
MainThreadSupport getMainThreadSupport() {
if (mainThreadSupport != null) {
return mainThreadSupport;
} else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
Object looperOrNull = getAndroidMainLooperOrNull();
return looperOrNull == null ? null :
new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
} else {
return null;
}
}
Object getAndroidMainLooperOrNull() {
try {
return Looper.getMainLooper();
} catch (RuntimeException e) {
// Not really a functional Android (e.g. "Stub!" maven dependencies)
return null;
}
}
可以看到这里通过主线程的Looper:Looper.getMainLooper()来创建了一个MainThreadSupport.AndroidHandlerMainThreadSupport。
class AndroidHandlerMainThreadSupport implements MainThreadSupport {
private final Looper looper;
public AndroidHandlerMainThreadSupport(Looper looper) {
this.looper = looper;
}
@Override
public boolean isMainThread() {
return looper == Looper.myLooper();
}
@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, looper, 10);
}
}
其中的createPoster通过传入EventBus和looper来创建一个HandlerPoster,所以最终的逻辑是在HandlerPoster中进行。
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
//循环处理消息事件,避免重复sendMessage()
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
//避免长期占用主线程,间隔10ms重新sendMassage()
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
为了避免频繁的向主线程sendMessage(),EventBus的做法是在一个消息里尽可能多的处理更多的消息事件,所以使用了while循环,持续以消息队列queue中获取消息。
同时,为了避免长期占有主线程,间隔10ms会重新发送sendMessage(),用于让出主线程的执行权,避免造成UI卡顿和ANR.
threadMode为MAIN时,可以确保时间的接收在主线程中,如果事件是在主线程呢个中发送的,则直接执行,否则使用mainThreadPoster来执行。在EventBus v3.1.1 新增了 MAIN_ORDERED,它不会区分当前线程,而是通通使用mainThreadPoster来处理,也就是必然会走一遍Handler的消息分发。
需要注意的是,在主线程中处理事件的时候,要求不能执行耗时操作。
想要让消息在子线程中处理,可以配置threadMode为BACKGROUND或者AYSNC。
BACKGROUND会区分当前发生事件的线程,是否是主线程,非主线程则直接分发事件,否则用backgroundPoster来处理事件。BackgroundPoster也是实现了Poster接口,其中也维护了一个用链表实现的消息队列PendingPostQueue,内部使用了EventBus 的 executorService 线程池对象去执行。
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
//volatile确保多线程下的可见性
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
//当前有线程执行时,不重复执行execute()
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
在 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,只在必要且合理的情况下,才去使用它。
1.EventBus可以通过threadMode来配置接收事件的线程
2.MAIN和MAIN_ORDERED都会在主线程中接收事件,区别在于是否区分发送事件的线程是否是主线程。
3.BACKGROUND确保在子线程中接收线程,会通过线程池,使用一个线程循环处理所有的事件。所以事件的执行时机,会收到事件队列前面的事件处理效率的影响。
4.ASYNC 确保在子线程中接收事件,区别于 BACKGROUND,ASYNC 会每次向线程池中发送任务,通过线程池的调度去执行。但是因为线程池采用的是无界队列,会导致 ASYNC 待处理的事件太多时,会导致 OOM。
参考链接:
https://juejin.im/post/5d81c6e8e51d4561ad65497c