EventBus源码剖析(3) -- 线程模式

December 3, 2018

文章列表:

一、简介

EventBus 消息支持通过不同线程模式发送并调起目标方法,以满足 Android 不同应用场景。通过订阅者的订阅方法可知,线程模式通过注解参数进行配置。

二、用法

注册类必须包含至少一个接收事件的方法,如果不满足此条件,订阅类注册到 EventBus 时会抛出异常。若没有设置 threadMode ,订阅方法默认在主线程调用。

1
2
3
4
5
6
@Subscribe(threadMode = ThreadMode.MAIN, sticky = false, priority = 0)
fun onEventReceived(event: UserEvent) {
    val name = event.name
    val age = event.age
    Log.i(TAG, "Event received, Name: $name, Age: $age.")
}

三、类介绍

Subscribe 接口是用于修饰订阅方法的注解。

1
2
3
4
5
6
7
8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    ....
    ....
}

EventBus 支持的线程模式都保存在枚举类中。每个订阅者方法含有默认线程模式,该模式决定如何调用订阅者方法。

1
public enum ThreadMode

四、线程模式

ThreadMode 枚举类包含5种线程模式:

EventBus-Publish-Subscribe

4.1 POSTING

订阅方法在发布线程上调起,这是 EventBus 的默认线程模式。因为能完全避免线程切换,意味着此线程模式的事件分发能减少性能开销。所以可在短时间、主线程完成的任务,本模式是首要推荐的。就是说此模式处理事件时,方法需要迅速执行完毕,并返回方法避免阻塞(主线程)发布线程。

4.2 MAIN

如果 EventBusAndroid 中使用,订阅者会在主线程中进行调用。如果消息发布线程就是主线程,则订阅方法直接调用,并阻塞发布线程。否则,事件在消息队列等待主线程(非阻塞性的)分派。

由于接收者方法执行在主线程上,所以方法逻辑应轻巧,或在内部开启子线程执行重量级操作,以此避免阻塞主线程运行。推理可知,如果订阅方法后续还有更多事件需要在主线程上发出,那么这些事件也会受到该次阻塞的影响。

如果 EventBus 所在环境不是 Android ,则本模式的执行行为和 POSTING 模式相同。

4.3 MAIN_ORDERED

Android 中订阅者方法也是在主线程中被调用。但和 MAIN 模式不同的,是本模式的消息进入属于主线程的消息队列等待执行,而不是阻塞主线程直接运行。

4.4 BACKGROUND

Android 中,此模式的订阅者会在后台线程调用。如果发布消息的线程不是主线程,则订阅者方法直接在发布线程上调用。如果消息发布线程是主线程,EventBus 会使用单个后台线程,该线程会顺序分派所有事件。如果订阅者使用此种模式,就应该快速从方法中返回,避免阻塞这个单一后台线程的运行。在非 Android 直接使用一个后台线程,不区分主线程。

4.5 ASYNC

订阅者会在分离的线程上调用。此线程独立于事件发布线程和主线程。使用这种模式时,发送事件不会等待订阅者方法。可使用这个模式处理订阅者方法的耗时事件,例如:进行网络访问。为了避免同时触发大量、长期运行的订阅者方法,EventBus 使用了线程池,从已完成的异步订阅者通知复用线程。

实现

上文提到实现方法 EventBus.postToSubscription() 的逻辑就不难理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 {
                // 处在其他线程,向主线程Handler发送消息
                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;

        // 传入未知threadMode引起异常
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

在消息发送者所在线程上调用目标方法。

1
2
3
4
5
6
7
8
9
10
11
12
void invokeSubscriber(Subscription subscription, Object event) {
    try {
        // subscription.subscriber:订阅方法所在实例
        // event:调用时传递给方法的参数,即方法接收的消息
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        // 捕获订阅者方法运行时异常,避免EventBus崩溃
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}