Android EventBus 的源码解析

1、EventBus 的使用

1.1 EventBus 简介

EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。

首先是 EventBus 的三个重要角色

  1. Event:事件,它可以是任意类型,EventBus 会根据事件类型进行全局的通知。
  2. Subscriber:事件订阅者,在 EventBus 3.0 之前我们必须定义以onEvent开头的那几个方法,分别是 onEvent()onEventMainThread()onEventBackgroundThread()onEventAsync(),而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING
  3. Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用 EventBus.getDefault() 就可以得到一个EventBus对象,然后再调用 post(Object) 方法发布事件即可。

其次是 EventBus 的四种线程模型(EventBus3.0),分别是:

  1. POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
  2. MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
  3. BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
  4. ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。
    1.2 使用 EventBus
    在使用之前先要引入如下依赖:
implementation 'org.greenrobot:eventbus:3.1.1'

然后,我们定义一个事件的封装对象。在程序内部就使用该对象作为通信的信息:

public class MessageWrap {

    public final String message;

    public static MessageWrap getInstance(String message) {
        return new MessageWrap(message);
    }

    private MessageWrap(String message) {
        this.message = message;
    }
}

然后,我们定义一个 Activity 要拿过来测试事件发布的效果:

@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY1)
public class EventBusActivity1 extends CommonActivity<ActivityEventBus1Binding> {

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        // 为按钮添加添加单击事件
        getBinding().btnReg.setOnClickListener(v -> EventBus.getDefault().register(this));
        getBinding().btnNav2.setOnClickListener( v ->
                ARouter.getInstance()
                        .build(BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
                        .navigation());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onGetMessage(MessageWrap message) {
        getBinding().tvMessage.setText(message.message);
    }
}

这里我们当按下按钮的时候向 EventBus 注册监听,然后按下另一个按钮的时候跳转到拎一个 Activity,并在另一个 Activity 发布我们输入的事件。在上面的 Activity 中,我们会添加一个监听的方法,即 onGetMessage(),这里我们需要为其加入注解 @Subscribe 并指定线程模型为主线程 MAIN。最后,就是在 Activity 的 onDestroy() 方法中取消注册该 Activity。

下面是另一个 Activity 的定义,在这个 Activity 中,我们当按下按钮的时候从 EditText 中取出内容并进行发布,然后我们退出到之前的 Activity,以测试是否正确监听到发布的内容:

@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
public class EventBusActivity2 extends CommonActivity<ActivityEventBus2Binding> {

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        getBinding().btnPublish.setOnClickListener(v -> publishContent());
    }

    private void publishContent() {
        String msg = getBinding().etMessage.getText().toString();
        EventBus.getDefault().post(MessageWrap.getInstance(msg));
        ToastUtils.makeToast("Published : " + msg);
    }
}

根据测试的结果,我们的确成功地接收到了发送的信息。

1.3 黏性事件

所谓的黏性事件,就是指发送了该事件之后再订阅者依然能够接收到的事件。使用黏性事件的时候有两个地方需要做些修改。一个是订阅事件的地方,这里我们在先打开的 Activity 中注册监听黏性事件:

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onGetStickyEvent(MessageWrap message) {
    String txt = "Sticky event: " + message.message;
    getBinding().tvStickyMessage.setText(txt);
}

另一个是发布事件的地方,这里我们在新的开的 Activity 中发布黏性事件。即调用 EventBus 的 postSticky() 方法来发布事件:

private void publishStickyontent() {
    String msg = getBinding().etMessage.getText().toString();
    EventBus.getDefault().postSticky(MessageWrap.getInstance(msg));
    ToastUtils.makeToast("Published : " + msg);
}

按照上面的模式,我们先在第一个 Activity 中打开第二个 Activity,然后在第二个 Activity 中发布黏性事件,并回到第一个 Activity 注册 EventBus。根据测试结果,当按下注册按钮的时候,会立即触发上面的订阅方法从而获取到了黏性事件。

1.4 优先级
在 @Subscribe 注解中总共有3个参数,上面我们用到了其中的两个,这里我们使用以下第三个参数,即 priority。它用来指定订阅方法的优先级,是一个整数类型的值,默认是 0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。

为了对优先级进行测试,这里我们需要对上面的代码进行一些修改。这里,我们使用一个布尔类型的变量来判断是否应该取消事件的分发。我们在一个较高优先级的方法中通过该布尔值进行判断,如果为 true 就停止该事件的继续分发,从而通过低优先级的订阅方法无法获取到事件来证明优先级较高的订阅方法率先获取到了事件。

这里有几个地方需要注意:

只有当两个订阅方法使用相同的ThreadMode参数的时候,它们的优先级才会与priority指定的值一致;
只有当某个订阅方法的ThreadMode参数为POSTING的时候,它才能停止该事件的继续分发。
所以,根据以上的内容,我们需要对代码做如下的调整:

// 用来判断是否需要停止事件的继续分发
private boolean stopDelivery = false;

@Override
protected void doCreateView(Bundle savedInstanceState) {
    // ...

    getBinding().btnStop.setOnClickListener(v -> stopDelivery = true);
}

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0)
public void onGetMessage(MessageWrap message) {
    getBinding().tvMessage.setText(message.message);
}

// 订阅方法,需要与上面的方法的threadMode一致,并且优先级略高
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 1)
public void onGetStickyEvent(MessageWrap message) {
    String txt = "Sticky event: " + message.message;
    getBinding().tvStickyMessage.setText(txt);
    if (stopDelivery) {
        // 终止事件的继续分发
        EventBus.getDefault().cancelEventDelivery(message);
    }
}

即我们在之前的代码之上增加了一个按钮,用来将 stopDelivery 的值置为 true。该字段随后将会被用来判断是否要终止事件的继续分发,因为我们需要在代码中停止事件的继续分发,所以,我们需要将上面的两个订阅方法的 threadMode 的值都置为ThreadMode.POSTING

按照,上面的测试方式,首先我们在当前的 Activity 注册监听,然后跳转到另一个 Activity,发布事件并返回。第一次的时候,这里的两个订阅方法都会被触发。然后,我们按下停止分发的按钮,并再次执行上面的逻辑,此时只有优先级较高的方法获取到了事件并将该事件终止。

上面的内容是 EventBus 的基本使用方法,相关的源码参考:Github

2、源码分析

在分析 EventBus 源码的时候,我们先从获取一个 EventBus 实例的方法入手,然后再分别看一下它的注册、取消注册、发布事件以及触发观察方法的代码是如何实现的。在下面的文章中我们将会回答以下几个问题:

在 EventBus 中,使用 @Subscribe 注解的时候指定的 ThreadMode 是如何实现在不同线程间传递数据的?
使用注解和反射的时候的效率问题,是否会像 Guava 的 EventBus 一样有缓存优化?
黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?

2.1 获取实例的过程

在创建 EventBus 实例的时候,一种方式是按照我们上面的形式,通过 EventBus 的静态方法 getDefault() 来获取一个实例。getDefault() 本身会调用其内部的构造方法,通过传入一个默认 的EventBusBuilder 来创建 EventBus。此外,我们还可以直接通过 EventBus 的 builder() 方法获取一个 EventBusBuilder 的实例,然后通过该构建者模式来个性化地定制自己的 EventBus。即:

// 静态的单例实例
static volatile EventBus defaultInstance;

// 默认的构建者
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

// 实际上使用了DCL双检锁机制,这里简化了一下
public static EventBus getDefault() {
    if (defaultInstance == null) defaultInstance = new EventBus();
    return defaultInstance;
}

public EventBus() {
    this(DEFAULT_BUILDER);
}

// 调用getDefault的时候,最终会调用该方法,使用DEFAULT_BUILDER创建一个实例
EventBus(EventBusBuilder builder) {
    // ...
}

// 也可以使用下面的方法获取一个构建者,然后使用它来个性化定制EventBus
public static EventBusBuilder builder() {
    return new EventBusBuilder();
}

2.2 注册

当调用 EventBus 实例的 register() 方法的时候,会执行下面的逻辑:

public void register(Object subscriber) {
    // 首席会获取注册的对象的类型
    Class<?> subscriberClass = subscriber.getClass();
    // 然后获取注册的对象的订阅方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // 对当前实例加锁,并不断执行监听的逻辑
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 对订阅方法进行注册
            subscribe(subscriber, subscriberMethod);
        }
    }
}

这里的 SubscriberMethod 封装了订阅方法(使用 @Subscribe 注解的方法)类型的信息,它的定义如下所示。从下面可以的代码中我们可以看出,实际上该类就是通过几个字段来存储 @Subscribe 注解中指定的类型信息,以及一个方法的类型变量。

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;

    // ...
}

register() 方法通过 subscriberMethodFinder 实例的 findSubscriberMethods() 方法来获取该观察者类型中的所有订阅方法,然后将所有的订阅方法分别进行订阅。下面我们先看下查找订阅者的方法。

查找订阅者的订阅方法
下面是 SubscriberMethodFinder 中的 findSubscriberMethods() 方法:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 这里首先从缓存当中尝试去取该订阅者的订阅方法
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    // 当缓存中没有找到该观察者的订阅方法的时候使用下面的两种方法获取方法信息
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException(...);
    } else {
        // 将获取到的订阅方法放置到缓存当中
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

这里我们先从缓存当中尝试获取某个观察者中的所有订阅方法,如果没有可用缓存的话就从该类中查找订阅方法,并在返回结果之前将这些方法信息放置到缓存当中。这里的 ignoreGeneratedIndex 参数表示是否忽略注解器生成的 MyEventBusIndex,该值默认为 false。然后,我们会进入到下面的方法中获取订阅方法信息;

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 这里通过FindState对象来存储找到的方法信息
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    // 这里是一个循环操作,会从当前类开始遍历该类的所有父类
    while (findState.clazz != null) {
        // 获取订阅者信息
        findState.subscriberInfo = getSubscriberInfo(findState); // 1
        if (findState.subscriberInfo != null) {
            // 如果使用了MyEventBusIndex,将会进入到这里并获取订阅方法信息
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 未使用MyEventBusIndex将会进入这里使用反射获取方法信息
            findUsingReflectionInSingleClass(findState); // 2
        }
        // 将findState.clazz设置为当前的findState.clazz的父类
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

在上面的代码中,会从当前订阅者类开始直到它最顶层的父类进行遍历来获取订阅方法信息。这里在循环的内部会根据我们是否使用了 MyEventBusIndex 走两条路线,对于我们没有使用它的,会直接使用反射来获取订阅方法信息,即进入2处。

下面是使用反射从订阅者中得到订阅方法的代码:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // 获取该类中声明的所有方法
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    // 对方法进行遍历判断
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        // 这里会对方法的修饰符进行校验
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // 这里对方法的输入参数进行校验
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                // 获取方法的注解,用来从注解中获取注解的声明信息
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    // 获取该方法的第一个参数
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 最终将封装之后的方法塞入到列表中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(...);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(...);
        }
    }
}

这里会对当前类中声明的所有方法进行校验,并将符合要求的方法的信息封装成一个SubscriberMethod对象塞到列表中。

注册订阅方法

直到了如何拿到所有的订阅方法之后,我们回到之前的代码,看下订阅过程中的逻辑:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    // 将所有的观察者和订阅方法封装成一个Subscription对象
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod); // 1
    // 尝试从缓存中根据事件类型来获取所有的Subscription对象
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); // 2
    if (subscriptions == null) {
        // 指定的事件类型没有对应的观察对象的时候
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException(...);
        }
    }

    // 这里会根据新加入的方法的优先级决定插入到队列中的位置
    int size = subscriptions.size(); // 2
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    // 这里又会从“订阅者-事件类型”列表中尝试获取该订阅者对应的所有事件类型
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); // 3
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 如果是黏性事件还要进行如下的处理
    if (subscriberMethod.sticky) { // 4
        if (eventInheritance) {
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    // 这里会向该观察者通知所有的黏性事件
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}
```

这里涉及到了几个集合,它们是用来做缓存的,还有就是来维护观察者、事件类型和订阅方法之间的关系的。注册观察的方法比较长,我们可以一点一点来看。首先,会在代码1处将观察者和订阅方法封装成一个 Subscription 对象。然后,在2处用到了 CopyOnWriteArrayList 这个集合,它是一种适用于多读写少场景的数据结构,是一种线程安全的数组型的数据结构,主要用来存储一个事件类型所对应的全部的 Subscription 对象。EventBus在这里通过一个 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 类型的哈希表来维护这个映射关系。然后,我们的程序执行到2处,在这里会对 Subscription 对象的列表进行遍历,并根据订阅方法的优先级,为当前的 Subscription 对象寻找一个合适的位置。3的地方主要的逻辑是获取指定的观察者对应的全部的观察事件类型,这里也是通过一个哈希表来维护这种映射关系的。然后,在代码 4 处,程序会根据当前的订阅方法是否是黏性的,来决定是否将当前缓存中的信息发送给新订阅的方法。这里会通过 checkPostStickyEventToSubscription() 方法来发送信息,它内部的实现的逻辑和 post() 方法类似,我们不再进行说明。

取消注册的逻辑比较比较简单,基本上就是注册操作反过来——将当前订阅方法的信息从缓存中踢出来,我们不再进行分分析。下面我们分析另一个比较重要的地方,即发送事件相关的逻辑。

2.3 通知

通知的逻辑相对来说会比较复杂一些,因为这里面涉及一些线程之间的操作。我们看下下面的代码吧:

public void post(Object event) {
    // 这里从线程局部变量中取出当前线程的状态信息
    PostingThreadState postingState = currentPostingThreadState.get();
    // 这里是以上线程局部变量内部维护的一个事件队列
    List<Object> eventQueue = postingState.eventQueue;
    // 将当前要发送的事件加入到队列中
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            // 不断循环来发送事件
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState); // 1
            }
        } finally {
            // 恢复当前线程的信息
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

这里的 currentPostingThreadState 是一个 ThreadLocal 类型的变量,其中存储了对应于当前线程的 PostingThreadState 对象,该对象中存储了当前线程对应的事件列表和线程的状态信息等。从上面的代码中可以看出,post() 方法会在1处不断从当前线程对应的队列中取出事件并进行发布。下面我们看以下这里的 postSingleEvent() 方法。

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
        // 这里向上查找该事件的所有父类
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            // 对上面的事件进行处理
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    // 找不到该事件的异常处理
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent 
                && eventClass != NoSubscriberEvent.class 
                && eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

在上面的代码中,我们会根据 eventInheritance 的值决定是否要同时遍历当前事件的所有父类的事件信息并进行分发。如果设置为 true 就将执行这一操作,并最终使用 postSingleEventForEventType 对每个事件类型进行处理。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    // 获取指定的事件对应的所有的观察对象
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        // 遍历观察对象,并最终执行事件的分发操作
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

在上面的代码中,我们会通过传入的事件类型到缓存中取寻找它对应的全部的 Subscription,然后对得到的 Subscription 列表进行遍历,并依次调用 postToSubscription() 方法执行事件的发布操作。下面是 postToSubscription() 方法的代码,这里我们会根据订阅方法指定的 threadMode 信息来执行不同的发布策略。

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 {
                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(...);
    }
}

在上面的方法中,会根据当前的线程状态和订阅方法指定 的 threadMode 信息来决定合适触发方法。这里的 invokeSubscriber() 会在当前线程中立即调用反射来触发指定的观察者的订阅方法。否则会根据具体的情况将事件加入到不同的队列中进行处理。这里的mainThreadPoster 最终继承自 Handler,当调用它的 enqueue() 方法的时候,它会发送一个事件并在它自身的 handleMessage() 方法中从队列中取值并进行处理,从而达到在主线程中分发事件的目的。这里的 backgroundPoster 实现了 Runnable 接口,它会在调用 enqueue() 方法的时候,拿到 EventBus 的 ExecutorService 实例,并使用它来执行自己。在它的 run() 方法中会从队列中不断取值来进行执行。

总结

以上就是Android中的EventBus的源码分析,这里我们回答之前提出的几个问题来作结:

在EventBus中,使用 @Subscribe 注解的时候指定的 ThreadMode 是如何实现在不同线程间传递数据的?
要求主线程中的事件通过 Handler 来实现在主线程中执行,非主线程的方法会使用 EventBus 内部的 ExecutorService 来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的 ThreadMode 指定的线程状态来决定何时触发方法。非主线程的逻辑会在 post() 的时候加入到一个队列中被随后执行。

使用注解和反射的时候的效率问题,是否会像 Guava 的 EventBus 一样有缓存优化?
内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像 Guava 一样使用软引用之类方式进行优化,即一直是强引用类型的。

黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?
黏性事件会通过 EventBus 内部维护的一个事件类型-黏性事件的哈希表存储,当注册一个观察者的时候,如果发现了它内部有黏性事件监听,会执行 post() 类似的逻辑将事件立即发送给该观察者。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 在这篇文章中,我会注重分析Android中的EventBus的实现原理,如果你之前还没有使用过该框架,那么可以参考...
    开发者如是说阅读 12,640评论 2 21
  • 在《Android EventBus使用》博文中,我们已经对EventBus的使用进行了分析,在这篇博文中,我们就...
    24K男阅读 458评论 0 5
  • 前言 上一篇文章自己对EventBus的用法进行了简单的叙述,然后自己又去研究了一下EventBus3.0源码也参...
    阿木木丶涛阅读 233评论 0 1
  • 基于最新的 3.1.1 分析 前言 之前分析的都是官方库的一些源码,现在打算尝试分析一些比较优秀的第三方开源库,选...
    没有颜色的菜阅读 166评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,495评论 16 22