本文共 19327 字,大约阅读时间需要 64 分钟。
EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
首先是 EventBus 的三个重要角色
onEvent()
、onEventMainThread()
、onEventBackgroundThread()
和 onEventAsync()
,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe
,并且指定线程模型,默认是POSTING
。EventBus.getDefault()
就可以得到一个EventBus对象,然后再调用 post(Object)
方法发布事件即可。其次是 EventBus 的四种线程模型(EventBus3.0),分别是:
在使用之前先要引入如下依赖:
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{ @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{ @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); }}
根据测试的结果,我们的确成功地接收到了发送的信息。
所谓的黏性事件,就是指发送了该事件之后再订阅者依然能够接收到的事件。使用黏性事件的时候有两个地方需要做些修改。一个是订阅事件的地方,这里我们在先打开的 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。根据测试结果,当按下注册按钮的时候,会立即触发上面的订阅方法从而获取到了黏性事件。
在 @Subscribe
注解中总共有3个参数,上面我们用到了其中的两个,这里我们使用以下第三个参数,即 priority
。它用来指定订阅方法的优先级,是一个整数类型的值,默认是 0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。
为了对优先级进行测试,这里我们需要对上面的代码进行一些修改。这里,我们使用一个布尔类型的变量来判断是否应该取消事件的分发。我们在一个较高优先级的方法中通过该布尔值进行判断,如果未 true
就停止该事件的继续分发,从而通过低优先级的订阅方法无法获取到事件来证明优先级较高的订阅方法率先获取到了事件。
这里有几个地方需要注意:
ThreadMode
参数的时候,它们的优先级才会与priority
指定的值一致;ThreadMode
参数为POSTING
的时候,它才能停止该事件的继续分发。所以,根据以上的内容,我们需要对代码做如下的调整:
// 用来判断是否需要停止事件的继续分发private boolean stopDelivery = false;@Overrideprotected 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 的基本使用方法,相关的源码参考:。
在分析 EventBus 源码的时候,我们先从获取一个 EventBus 实例的方法入手,然后再分别看一下它的注册、取消注册、发布事件以及触发观察方法的代码是如何实现的。在下面的文章中我们将会回答以下几个问题:
@Subscribe
注解的时候指定的 ThreadMode
是如何实现在不同线程间传递数据的?在创建 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) { // ...}// 也可以使用下面的方法获取一个构建者,然后使用它来个性化定制EventBuspublic static EventBusBuilder builder() { return new EventBusBuilder();}
当调用 EventBus 实例的 register()
方法的时候,会执行下面的逻辑:
public void register(Object subscriber) { // 首席会获取注册的对象的类型 Class subscriberClass = subscriber.getClass(); // 然后获取注册的对象的订阅方法 ListsubscriberMethods = 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()
方法:
ListfindSubscriberMethods(Class subscriberClass) { // 这里首先从缓存当中尝试去取该订阅者的订阅方法 List 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 ListfindUsingInfo(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对象 CopyOnWriteArrayListsubscriptions = 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 > 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 , Object>> entries = stickyEvents.entrySet(); for (Map.Entry , 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()
方法类似,我们不再进行说明。
取消注册的逻辑比较比较简单,基本上就是注册操作反过来——将当前订阅方法的信息从缓存中踢出来,我们不再进行分分析。下面我们分析另一个比较重要的地方,即发送事件相关的逻辑。
通知的逻辑相对来说会比较复杂一些,因为这里面涉及一些线程之间的操作。我们看下下面的代码吧:
public void post(Object event) { // 这里从线程局部变量中取出当前线程的状态信息 PostingThreadState postingState = currentPostingThreadState.get(); // 这里是以上线程局部变量内部维护的一个事件队列 List
这里的 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> 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) { // 获取指定的事件对应的所有的观察对象 CopyOnWriteArrayListsubscriptions; 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的源码分析,这里我们回答之前提出的几个问题来作结:
@Subscribe
注解的时候指定的 ThreadMode
是如何实现在不同线程间传递数据的?要求主线程中的事件通过 Handler
来实现在主线程中执行,非主线程的方法会使用 EventBus 内部的 ExecutorService
来执行。实际在触发方法的时候会根据当前线程的状态和订阅方法的 ThreadMode
指定的线程状态来决定何时触发方法。非主线程的逻辑会在 post()
的时候加入到一个队列中被随后执行。
内部使用了缓存,确切来说就是维护了一些映射的关系。但是它的缓存没有像 Guava 一样使用软引用之类方式进行优化,即一直是强引用类型的。
黏性事件会通过 EventBus 内部维护的一个事件类型-黏性事件
的哈希表存储,当注册一个观察者的时候,如果发现了它内部有黏性事件监听,会执行 post()
类似的逻辑将事件立即发送给该观察者。
如果您喜欢我的文章,可以在以下平台关注我:
转载地址:http://lkurf.baihongyu.com/