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> {@Overrideprotected 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());}@Overrideprotected 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> {@Overrideprotected 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 就停止该事件的继续分发,从而通过低优先级的订阅方法无法获取到事件来证明优先级较高的订阅方法率先获取到了事件。

这里有几个地方需要注意

  1. 只有当两个订阅方法使用相同的ThreadMode参数的时候,它们的优先级才会与priority指定的值一致;
  2. 只有当某个订阅方法的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 实例的方法入手,然后再分别看一下它的注册、取消注册、发布事件以及触发观察方法的代码是如何实现的。在下面的文章中我们将会回答以下几个问题:

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

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); // 1if (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); // 2if (subscriptions == null) {// 指定的事件类型没有对应的观察对象的时候subscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions);} else {if (subscriptions.contains(newSubscription)) {throw new EventBusException(...);}}// 这里会根据新加入的方法的优先级决定插入到队列中的位置int size = subscriptions.size(); // 2for (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); // 3if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);// 如果是黏性事件还要进行如下的处理if (subscriberMethod.sticky) { // 4if (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的源码分析,这里我们回答之前提出的几个问题来作结:

  1. 在EventBus中,使用 @Subscribe 注解的时候指定的 ThreadMode 是如何实现在不同线程间传递数据的?

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

  1. 使用注解和反射的时候的效率问题,是否会像 Guava 的 EventBus 一样有缓存优化?

内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像 Guava 一样使用软引用之类方式进行优化,即一直是强引用类型的。

  1. 黏性事件是否是通过内部维护了之前发布的数据来实现的,是否使用了缓存?

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


如果您喜欢我的文章,可以在以下平台关注我:

  • 个人主页:shouheng88.github.io/
  • 掘金:juejin.im/user/585555…
  • Github:github.com/Shouheng88
  • CSDN:blog.csdn.net/github_3518…
  • 微博:weibo.com/u/540115211…

Android 源码分析之 EventBus 的源码解析相关推荐

  1. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  2. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  3. 【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  4. Flume 1.7 源码分析(一)源码编译

    Flume 1.7 源码分析(一)源码编译 Flume 1.7 源码分析(二)整体架构 Flume 1.7 源码分析(三)程序入口 1 说明 Flume是Cloudera提供的一个高可用的,高可靠的, ...

  5. Storm源码分析之四: Trident源码分析

    Storm源码分析之四: Trident源码分析 @(STORM)[storm] Storm源码分析之四 Trident源码分析 一概述 0小结 1简介 2关键类 1Spout的创建 2spout的消 ...

  6. 【SemiDrive源码分析】【Yocto源码分析】02 - yocto/meta-openembedded目录源码分析

    [SemiDrive源码分析][Yocto源码分析]02 - yocto/meta-openembedded目录源码分析 一.meta-openembedded 目录 本 SemiDrive源码分析 ...

  7. 【SemiDrive源码分析】【Yocto源码分析】07 - core-image-base-x9h_ref_serdes.rootfs.ext4 文件系统是如何生成的

    [SemiDrive源码分析][Yocto源码分析]07 - core-image-base-x9h_ref_serdes.rootfs.ext4 文件系统是如何生成的 1.core-image-ba ...

  8. android6.0源码分析之AMS服务源码分析

    activitymanagerservice服务源码分析 1.ActivityManagerService概述 ActivityManagerService(以下简称AMS)作为Android中最核心 ...

  9. Java源码详解六:ConcurrentHashMap源码分析--openjdk java 11源码

    文章目录 注释 类的继承与实现 数据的存储 构造函数 哈希 put get 扩容 本系列是Java详解,专栏地址:Java源码分析 ConcurrentHashMap 官方文档:ConcurrentH ...

最新文章

  1. 为方便ATS管理建立的一些命令别名
  2. class ts 扩展方法_JUnit 5自定义扩展
  3. python3编码声明_python3编码问题汇总
  4. 关于Hibernate 3
  5. ac3168无线网卡驱动下载_更换BCM94360CS2网卡,拯救黑苹果无线网络
  6. 何谓 SQL 注入,这个漫画告诉你
  7. 树状数组相关应用之二元变量结构体组队问题
  8. 同步、异步、堵塞、非堵塞概念总结
  9. 【基础教程】基于matlab工具voicebox函数中文说明【含Matlab源码 032期】
  10. linux 软件源码安装教程,linux源码安装软件步骤
  11. windows 2012 抓明文密码方法
  12. java删除文件夹及下面的所有文件
  13. 仿ios相机apk_icamera仿苹果安卓版-icamera仿苹果软件下载-多多root网
  14. h5页面分享朋友,朋友圈设置缩略图,自定义标题,描述!
  15. Midjourney之外21款免费的AI Image画图网站集合
  16. 访问hfds报错AccessControlException
  17. (LeetCode C++)跳跃游戏
  18. ubuntu18.04显卡和触摸板驱动的问题
  19. 与一位转行做滴滴司机的前程序员对话引发的思考
  20. commvault备份mysql数据库_Commvault数据备份恢复平台-功能测试报告.pdf

热门文章

  1. java,使用get、post请求url地址
  2. log4j打印mybatis sql语句
  3. Android开发3g、wap、net的区别
  4. 博客园模板 样式优化
  5. apache 模块编译选项
  6. 复制数据表的两种情况。
  7. mysql 原理 ~ DDL之在线DDL
  8. .NET控件名称缩写一览表 zz
  9. 如何开发一个Node脚手架
  10. iOS autolayout