EventBus源码赏析四 —— 线程切换

816 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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);
    }
}

这种模型下,任务的执行交给了线程池,两个任务之间完全独立,所以可以做耗时操作。