EventBus详解
来源:互联网 发布:装修公司半包猫腻知乎 编辑:程序博客网 时间:2024/04/30 02:21
EventBus详解
1. 功能介绍
1.1 EventBus
EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件。
事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,
相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。
1.2 相关概念
事件(Event):又可称为消息,本文中统一用事件表示。其实就是一个对象,可以是网络请求返回的字符串,也可以是某个开关状态等等。事件类型(EventType)指事件所属
的 Class。事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近
一个 Sticky 事件。
订阅者(Subscriber):订阅某种事件类型的对象。当有发布者发布这类事件后,EventBus 会执行订阅者的 onEvent 函数,这个函数叫事件响应函数。订阅者通过 register
接口订阅某个事件类型,unregister 接口退订。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。
发布者(Publisher):发布某事件的对象,通过 post 接口发布事件。
1.3 订阅者、发布者、EventBus 关系图
1.4 EventBus事件响应的流程
2.EventBus的使用
EventBus.getDefault().register(this);//订阅事件
EventBus.getDefault().post(object);//发布事件
大多情况下,都会在onCreate中进行register,在onDestory中进行unregister ;
在onCreate注册EventBus: EventBus.getDefault().register(this);
意思是让EventBus扫描当前类,把所有onEvent开头的方法记录下来,如何记录呢?使用Map,Key为方法的参数类型,Value中包含我们的方法。
这样在onCreate执行完成以后,我们的onEventMainThread就已经以键值对的方式被存储到EventBus中了。
然后当子线程执行完毕,调用EventBus.getDefault().post(new ItemListEvent(Item.ITEMS))时,EventBus会根据post中实参的类型,去Map中查找对于的方法,于是找到了我们的onEventMainThread,最终调用反射去执行我们的方法
在onDestory中反注册 : EventBus.getDefault().unregister(this);
定义方法onEventMainThread(VideoInfo info)
如果方法名以onEvent开头,则代表要订阅一个事件,MainThread意思,这个方法最终要在UI线程执行;当事件发布的时候,这个方法就会被执行。
Activity端
public void OnEventMainThread(VideoInfo info) {
//根据接受的信息做一些事情,如果只需要知道对方的一些状态,不适用对方的发过来的信息也是可以使双方联系起来的
//如:seekbarAudio.setMax((int) mediaItem.getDuration());
//info为service(Activity等通信的另一端)传过来的数据对象
}
* 服务端 --- 服务端发送数据,OnEventMainThread(VideoInfo info)接收数据,类型一致
EventBus.getDefault().post(info);//将当前播放的对象传过去
3.EventBus的四种模式
EventBus包含4个ThreadMode:PostThread,MainThread,BackgroundThread,Async
MainThread我们已经不陌生了;我们已经使用过。
具体的用法,极其简单,方法名为:onEventPostThread, onEventMainThread,onEventBackgroundThread,onEventAsync即可
具体什么区别呢?
PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。EventBus是如何区分这四种模式的呢?
其实在EventBus内部使用了Map进行存储,键就是参数的Class类型。当你进行post的时候,根据post传入的参数,去找到匹配的方法,反射进行调用。
4.源码解析
- EventBus.java
EventBus 类负责所有对外暴露的 API,其中的 register()、post()、unregister() 函数配合上自定义的 EventType 及事件响应函数即可完成核心功能
对于变量的说明:
2.DEFAULT_BUILDER默认的 EventBus Builder。
3.eventTypesCache事件对应类型及其父类和实现的接口的缓存,以 eventType 为 key,元素为 Object 的 ArrayList 为 Value,Object 对象为 eventType 的父类或接口。 4.subscriptionsByEventType事件订阅者的保存队列,以 eventType 为 key,元素为Subscription的 ArrayList 为 Value,其中Subscription为订阅者信息,由 subscriber, subscriberMethod, priority 构成。
5.typesBySubscriber订阅者订阅的事件的保存队列,以 subscriber 为 key,元素为 eventType 的 ArrayList 为 Value。
6.stickyEventsSticky 事件保存队列,以 eventType 为 key,event 为元素,由此可以看出对于同一个 eventType 最多只会有一个 event 存在。
7.currentPostingThreadState当前线程的 post 信息,包括事件队列、是否正在分发中、是否在主线程、订阅者信息、事件实例、是否取消。
8.mainThreadPoster、backgroundPoster、asyncPoster事件主线程处理者、事件 Background 处理者、事件异步处理者。
9.subscriberMethodFinder订阅者响应函数信息存储和查找类。
10.executorService异步和 BackGround 处理方式的线程池。
11.throwSubscriberException当调用事件处理函数异常时是否抛出异常,默认为 false,建议通过
12.logSubscriberExceptions当调用事件处理函数异常时是否打印异常信息,默认为 true。
13.logNoSubscriberMessages当没有订阅者订阅该事件时是否打印日志,默认为 true。
14.sendSubscriberExceptionEvent当调用事件处理函数异常时是否发送 SubscriberExceptionEvent 事件,若此开关打开,订阅者可通过
15.sendNoSubscriberEvent当没有事件处理函数对事件处理时是否发送 NoSubscriberEvent 事件,若此开关打开,订阅者可通过
16.eventInheritance是否支持事件继承,默认为 true。
EventBus 默认可通过静态函数 getDefault 获取单例,当然有需要也可以通过 EventBusBuilder 或 构造函数新建一个 EventBus,每个新建的 EventBus 发布和订阅事件都是相互隔离的,即一个 EventBus 对象中的发布者发布事件,另一个 EventBus 对象中的订阅者不会收到该订阅。
public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; } public static EventBusBuilder builder() { return new EventBusBuilder(); }
(1) register 和 unregister
分别表示订阅事件和取消订阅。register 最底层函数有三个参数,分别为订阅者对象、是否是 Sticky 事件、优先级。
/** * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they * are no longer interested in receiving events. * <p/> * Subscribers have event handling methods that must be annotated by {@link Subscribe}. * The {@link Subscribe} annotation also allows configuration like {@link * ThreadMode} and priority. */ public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
在register 函数中会先根据订阅者类名去subscriberMethodFinder中查找当前订阅者所有事件响应函数,然后循环每一个事件响应函数,依次执行下面的 subscribe 函数
unregister:
/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
(2) subscribe
subscribe 函数分三步
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //第一步:通过subscriptionsByEventType得到该事件类型所有订阅者信息队列, // 根据优先级将当前订阅者信息插入到订阅者队列subscriptionsByEventType中; Class<?> eventType = subscriberMethod.eventType; /** * 这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; * 这个Map其实就是EventBus存储方法的地方,一定要记住! */ Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //第二步:在typesBySubscriber中得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅; List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); /** *第三步:检查这个事件是否是 Sticky 事件,如果是则从stickyEvents事件保存队列中取出该事件类型最后一个事件发送给当前订阅者。 * 判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。 * stickyEvent其实就是我们post时的参数。 */ if (subscriberMethod.sticky) { if (eventInheritance) { // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). 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); } } }
附 :checkPostStickyEventToSubscription的实现代码
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state) // --> Strange corner case, which we don't take care of here. postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper()); } }
附 :postToSubscription的实现代码
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) {//<span style="color:#FF0000;">根据事件的模式去执行相应的反射调用</span> case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(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); } }
(3) post、cancel 、removeStickyEvent
post 函数用于发布事件,cancel 函数用于取消某订阅者订阅的所有事件类型、removeStickyEvent 函数用于删除 sticky 事件。
A.post
post 函数流程图如下:
post 函数会首先得到当前线程的 post 信息PostingThreadState,其中包含事件队列,将当前事件添加到其事件队列中,然后循环调用 postSingleEvent 函数发布队列中的每个事件。
/** * post 函数会首先得到当前线程的 post 信息PostingThreadState, * 其中包含事件队列,将当前事件添加到其事件队列中,然后循环调用 postSingleEvent 函数发布队列中的每个事件。 * Posts the given event to the event bus. */public void post(Object event) { /** * currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState; * PostingThreadState包含了一个eventQueue和一些标志位。 */ PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); 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); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } }}
postSingleEvent 函数会先去eventTypesCache得到该事件对应类型的的父类及接口类型,没有缓存则查找并插入缓存。循环得到的每个类型和接口,调用 postSingleEventForEventType 函数发布每个事件到每个订阅者。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; /** * 根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class, * 以及父类和接口的Class类型;主要用于匹配,比如你传入Dog extends Dog,他会把Animal也装到该List中。 */ if (eventInheritance) { List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); //遍历所有的Class,到subscriptionsByEventType去查找subscriptions for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); //postSingleEventForEventType 函数在subscriptionsByEventType查找该事件订阅者订阅者队列, // 调用 postToSubscription 函数向每个订阅者发布事件。 subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } }}
postSingleEventForEventType 函数在subscriptionsByEventType查找该事件订阅者订阅者队列,调用 postToSubscription 函数向每个订阅者发布事件。
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; }
- EventBusBuilder.java
跟一般 Builder 类似,用于在需要设置参数过多时构造 EventBus。包含的属性也是 EventBus 的一些设置参数,build 函数用于新建 EventBus 对象,installDefaultEventBus 函数将当前设置应用于 Default EventBus。
该类提供了以下几个方法:
这里只介绍其中两个方法:
/** * By default, EventBus considers the event class hierarchy (subscribers to super classes will be notified). * Switching this feature off will improve posting of events. For simple event classes extending Object directly, * we measured a speed up of 20% for event posting. For more complex event hierarchies, the speed up should be * >20%. * However, keep in mind that event posting usually consumes just a small proportion of CPU time inside an app, * unless it is posting at high rates, e.g. hundreds/thousands of events per second. */public EventBusBuilder eventInheritance(boolean eventInheritance) { this.eventInheritance = eventInheritance; return this;}
这段英文大意是是:默认的情况下事件总线的事件类是有层次的(事件订阅者的父类也会被通知)。关闭这个特性将会提高事件提交的速度。我们通过测量得知,一个直接继承子Object的简单的事件类的提交速度为20%,一个复杂事件类事件提交的速度大于20%。然而,记住,对于App来说,提交事件通常只需要耗费一小部分的Cpu时间除非提交一个高消耗的时间例如:每秒要执行百或者千次的时间。
所以只要不是频率较高的事件类,事件提交的耗费是很小的。
/** * Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced * setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior. */public EventBusBuilder executorService(ExecutorService executorService) { this.executorService = executorService; return this;}
- SubscriberMethodFinder.java
订阅者响应函数信息存储和查找类,由 HashMap 缓存,以 ${subscriberClassName} 为 key,SubscriberMethod 对象为元素的 ArrayList 为 value。findSubscriberMethods 函数用于查找订阅者响应函数,如果不在缓存中,则遍历自己的每个函数并递归父类查找,查找成功后保存到缓存中。遍历及查找规则为:
a. 遍历 subscriberClass 每个方法;
b. 该方法不以java.、javax.、android.这些 SDK 函数开头,并以onEvent开头,表示可能是事件响应函数继续,否则检查下一个方法;
c. 该方法是否是 public 的,并且不是 ABSTRACT、STATIC、BRIDGE、SYNTHETIC 修饰的,满足条件则继续。其中 BRIDGE、SYNTHETIC 为编译器生成的一些函数修饰符;
d. 该方法是否只有 1 个参数,满足条件则继续;
e. 该方法名为 onEvent 则 threadMode 为ThreadMode.PostThread;
该方法名为 onEventMainThread 则 threadMode 为ThreadMode.MainThread;
该方法名为 onEventBackgroundThread 则 threadMode 为ThreadMode.BackgroundThread;
该方法名为 onEventAsync 则 threadMode 为ThreadMode.Async;
其他情况且不在忽略名单 (skipMethodVerificationForClasses) 中则抛出异常。
f. 得到该方法唯一的参数即事件类型 eventType,将这个方法、threadMode、eventType 一起构造 SubscriberMethod 对象放到 ArrayList 中。
g. 回到 b 遍历 subscriberClass 的下一个方法,若方法遍历结束到 h;
h. 回到 a 遍历自己的父类,若父类遍历结束回到 i;
i. 若 ArrayList 依然为空则抛出异常,否则会将 ArrayList 做为 value,${subscriberClassName} 做为 key 放到缓存 HashMap 中。 对于事件函数的查找有两个小的性能优化点:
a. 第一次查找后保存到了缓存中,即上面介绍的 HashMap b. 遇到 java. javax. android. 开头的类会自动停止查找
类中的 skipMethodVerificationForClasses 属性表示跳过哪些类中非法以 onEvent 开头的函数检查,若不跳过则会抛出异常。
PS:在此之前的版本 EventBus 允许自定义事件响应函数名称,缓存的 HashMap key 为 ${subscriberClassName}.${eventMethodName},这版本中此功能已经被去除。
- SubscriberMethod.java
订阅者事件响应函数信息,包括响应方法、线程 Mode、事件类型以及一个用来比较 SubscriberMethod 是否相等的特征值 methodString 共四个变量,其中 methodString 为 ${methodClassName}#${methodName}(${eventTypeClassName}。
- Subscription.java
订阅者信息,包括 subscriber 对象、事件响应方法 SubscriberMethod、优先级 priority。
- HandlerPoster.jva
事件主线程处理,对应ThreadMode.MainThread。继承自 Handler,enqueue 函数将事件放到队列中,并利用 handler 发送 message,handleMessage 函数从队列中取事件,invoke 事件响应函数处理。
- AsyncPoster.java
事件异步线程处理,对应ThreadMode.Async,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。
- BackgroundPoster.java
事件 Background 处理,对应ThreadMode.BackgroundThread,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。与 AsyncPoster.java 不同的是,BackgroundPoster 中的任务只在同一个线程中依次执行,而不是并发执行。
- PendingPost.java
订阅者和事件信息实体类,并含有同一队列中指向下一个对象的指针。通过缓存存储不用的对象,减少下次创建的性能消耗。
4.2.10 PendingPostQueue.java
通过 head 和 tail 指针维护一个PendingPost队列。HandlerPoster、AsyncPoster、BackgroundPoster 都包含一个此队列实例,表示各自的订阅者及事件信息队列,在事件到来时进入队列,处理时从队列中取出一个元素进行处理。
- SubscriberExceptionEvent.java
当调用事件处理函数异常时发送的 EventBus 内部自定义事件,通过 post 发送,订阅者可自行订阅这类事件进行处理。
- NoSubscriberEvent.java
当没有事件处理函数对事件处理时发送的 EventBus 内部自定义事件,通过 post 发送,订阅者可自行订阅这类事件进行处理。
- EventBusException.java
封装于 RuntimeException 之上的 Exception,只是覆盖构造函数,相当于一个标记,标记是属于 EventBus 的 Exception。
- ThreadMode.java
线程 Mode 枚举类,表示事件响应函数执行线程信息,包括ThreadMode.PostThread、ThreadMode.MainThread、ThreadMode.BackgroundThread、ThreadMode.Async四种。
参考了大牛的博客,目前正在学习中,有不对的见解,欢迎指正,谢谢
- EventBus详解
- EventBus详解
- EventBus详解
- EventBus详解
- EventBus详解
- EventBus详解
- EventBus详解
- EventBus详解
- Eventbus详解
- EventBus详解
- Android EventBus详解
- EventBus使用详解
- EventBus使用详解
- EventBus使用详解
- EventBus使用详解
- EventBus使用详解
- EventBus 使用详解
- 最新EventBus源码详解
- 手把手教从零开始在GitHub上使用Hexo搭建博客教程(四)-使用Travis自动部署Hexo(2)
- 设计模式(Design Pattern)系列之.NET专题
- UISearchBar控件
- 【可视化】数据图表可视化
- 利用OpenCV的calcHist绘制灰度直方图、H-S直方图、BGR直方图和自定义直方图的源码及说明
- EventBus详解
- 【腾讯Bugly干货】深入浅出 Retrofit,这么牛逼的框架你们还不来看看?
- DeepLearningNotes: Network In Network
- 一步一步教你写股票走势图——分时图二(自定义xy轴)
- 安卓View开发心得(四)
- UITextField
- ThreadLocal内部实现及应用场景
- 【Linux】Linux系统下的PCB结构(task_struct)
- mysql 5.7 64位 解压版安装