一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情。
使用@Subscribe(threadMode = ThreadMode.MAIN)
的时候会发现,EventBus支持订阅方法指定线程模型,ThreadMode是一个枚举类型,有以下取值
public enum ThreadMode {
POSTING,
MAIN,
MAIN_ORDERED,
BACKGROUND,
ASYNC
}
//ThreadMode threadMode() default ThreadMode.POSTING
可以看到ThreadMode的默认值为POSTING
- POSTING 订阅者的订阅方法将在发布事件的同一线程中被调用,这种模式下不会涉及线程切换,所以开销比较小,但是可能发布事件的线程是主线程,所以需要避免在订阅方法中处理耗时操作
- MAIN 订阅方法将在UI线程被调用,如果发布事件的线程也是UI线程,则可以直接执行调用订阅方法,否则会使用Handler切换到UI线程
- MAIN_ORDERED 与MAIN类似,只不过不管发布线程在不在UI线程,都会由Handler处理,这确保了post()的调用是非阻塞的
- BACKGROUND 订阅方法将在非UI线程被调用,如果发布事件的线程在UI线程,则会创建一个线程来执行订阅方法,否则直接执行,为避免后续事件的发送或调用,需要避免在订阅方法中处理耗时操作
- ASYNC 订阅方法的调用线程将独立于发送事件的线程,所以这种情况下可以做耗时操作,但是需要避免在同一时间进行大量的异步订阅,控制并发线程的数量。
线程切换
线程切换在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);
}
}
POSTING
POSTING线程模型最简单,直接在当前线程执行订阅方法即可
MAIN
MAIN线程模型需要再UI线程执行订阅方法,所以需要先判断当前线程是不是UI线程,如果是就直接执行,否则由mainThreadPoster处理
mainThreadPoster由MainThreadSupport的实现类DefaultAndroidMainThreadSupport提供
public class DefaultAndroidMainThreadSupport implements MainThreadSupport {
@Override
public boolean isMainThread() {
return Looper.getMainLooper() == Looper.myLooper();
}
@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);
}
}
DefaultAndroidMainThreadSupport不仅提供了mainThreadPoster,还提供了判断是不是在主线程的方法。
mainThreadPoster是Poster实现类HandlerPoster的实例
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
//省略构造方法
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();
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;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
PendingPostQueue是一个自定义的消息队列,存储了待处理的发布事件 enqueue()方法将订阅信息转化成PendingPost对象,然后放入发布队列queue,入队之后如果当前Handler处于激活状态,则会不断从队列取出事件然后执行,否则通过sendMessage()唤醒Handler继续执行。 handleMessage()方法是我们经常重写的,它使用while (true)开启循环,不断从queue取出事件执行,由于是在UI线程执行,所以不能无限制执行下去,因此HandlerPoster对连续执行时间限制在10ms,也就是maxMillisInsideHandleMessage参数,超时之后将退出循环,在下一次空闲时间再处理。
还有一点需要注意的是当前线程模型下如果发布事件的线程是MAIN线程,那么订阅方法会立即在MAIN中执行,所以可能会阻塞下一个事件的发送。
MAIN_ORDERED
MAIN_ORDERED与MAIN类似,也是由mainThreadPoster处理,不同的是它不关心发布事件的线程。
BACKGROUND
BACKGROUND线程模型需要先判断当前线程是不是UI线程,如果不是就直接执行,否则由BackgroundPoster的实例处理
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
//省略构造方法
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
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) {
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与HandlerPoster大致相似,只不过没有使用Handler,而是Runnable enqueue()方法中也是先把订阅信息封装成PendingPost对象放入queue,然后判断是否有任务正在执行,如果有就等待轮训,否则由EventBus线程池指定新的线程执行 run()方法中通过不断取出PendingPost然后执行,while (true)保证了相近的事件尽可能在同一线程执行,这意味着他们是串行执行的,所以不要在订阅方法中做耗时任务,防止事件延迟发送。queue.poll(1000)保证了线程不会一直存活,当超时没有任务的时候就会退出
ASYNC
ASYNC线程模型和BACKGROUND相似,只不过他没有加入同步操作
class AsyncPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
//省略构造方法
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
}
这种模型下,任务的执行交给了线程池,两个任务之间完全独立,所以可以做耗时操作。