05.EventBus事件设计
目录介绍
- 01.EventBus注册源码解析
- 1.1 EventBus.getDefault()获取对象
- 1.2 register(this)注册源码解析
- 1.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
- 02.EventBus事件分发解析
- 2.1 从post方法入手
- 2.2 什么是PostingThreadState?
- 2.3 PostingThreadState怎么获得?
- 2.4 来看看postSingleEvent方法里做了什么
- 2.5 接下来看看postSingleEventForEventType方法
- 2.6 接下来看看postToSubscription方法
- 2.7 整个流程图
- 2.8 总结一下整个事件分发的过程
- 03.EventBus取消注册解析
- 3.1 unregister(this)方法入手
- 3.2 再来看看unsubscribeByEventType(subscriber, eventType)
- 3.3 取消注册流程图
- 3.4 总结一下取消注册的过程
- 04.总结一下EventBus的工作原理
- 4.1 订阅逻辑
- 4.2 事件发送逻辑
- 4.3 取消逻辑
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
00.EventBus了解和使用
0.1 EventBus简介
- EventBus的三要素
- Event:事件 可以是任意类型的对象。
- Subscriber:事件订阅者 在EventBus3.0之前,消息处理的方法只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他们分别代表四种线程模型。 在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),四种线程模型下面会讲到。
- Publisher:事件发布者 可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()就好了,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
- EventBus的四种ThreadMode(线程模型)
- POSTING(默认): 如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
- MAIN: 事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。
- BACKGROUND: 如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
- ASYNC: 无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
- EventBus怎么调用** 代码如下: EventBus.getDefault().post(param);
- 调用原理简单理解为:
- 一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。
- 撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。
- 它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?
0.2 EventBus使用
- 自定义一个事件类
public class MessageEvent { }
- 在需要订阅事件的地方注册事件
EventBus.getDefault().register(this);
- 发送事件
EventBus.getDefault().post(messageEvent);
- 处理事件
- 3.0之后, 消息处理的方法可以随便取名。问题:(threadMode=ThreadMode.MAIN)是做什么用的?? 需要添加一个注解@Subscribe,并且要指定线程模型。如果没有添加,那就是默认为POSTING
@Subscribe(threadMode = ThreadMode.MAIN) public void Hhhh(MessageEvent messageEvent) { ... }
- 3.0之后, 消息处理的方法可以随便取名。问题:(threadMode=ThreadMode.MAIN)是做什么用的?? 需要添加一个注解@Subscribe,并且要指定线程模型。如果没有添加,那就是默认为POSTING
- 取消事件订阅
EventBus.getDefault().unregister(this);
- 使用弊端分析
- EventBus好处比较明显,它能够解耦和,将业务和视图分离,代码实现比较容易。而且3.0后,我们可以通过apt预编译找到订阅者,避免了运行期间的反射处理解析,大大提高了效率。当然EventBus也会带来一些隐患和弊端,如果滥用的话会导致逻辑的分散并造成维护起来的困难。另外大量采用EventBus代码的可读性也会变差。
01.EventBus注册源码解析
1.1 EventBus.getDefault()获取对象
- 先看源码,单例模式,使用了双重判断的方式,防止并发的问题,还能极大的提高效率
/** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
- 接着看一下如何构造对象
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); public EventBus() { this(DEFAULT_BUILDER); } EventBus(EventBusBuilder builder) { logger = builder.getLogger(); subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>(); stickyEvents = new ConcurrentHashMap<>(); mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; }
1.2 register(this)注册源码解析
- 首先看register(this)源码
- 这里直接获取subscriber字节码对象。
public void register(Object subscriber) { //首先获取订阅者的类对象 Class<?> subscriberClass = subscriber.getClass(); //用 subscriberMethodFinder 提供的方法,找到在 subscriber 这个类里面订阅的内容。 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { // for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
- 这里直接获取subscriber字节码对象。
- 接下来看findSubscriberMethods(subscriberClass)里面的源码
- 该方法的作用其实就是从订阅类中获取所有的订阅方法信息
- findSubscriberMethods找出一个SubscriberMethod的集合,也就是传进来的订阅者所有的订阅的方法,接下来遍历订阅者的订阅方法来完成订阅者的订阅操作。对于SubscriberMethod(订阅方法)类中,主要就是用保存订阅方法的Method对象、线程模式、事件类型、优先级、是否是粘性事件等属性。
- 源码分析:首先从缓存中查找,如果找到了就立马返回。如果缓存中没有的话,则根据 ignoreGeneratedIndex 选择如何查找订阅方法,ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex。最后,找到订阅方法后,放入缓存,以免下次继续查找。ignoreGeneratedIndex 默认就是false,可以通过EventBusBuilder来设置它的值。我们在项目中经常通过EventBus单例模式来获取默认的EventBus对象,也就是ignoreGeneratedIndex为false的情况,这种情况调用了findUsingInfo方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //首先从缓存中读取 List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //是否忽略注解器生成的MyEventBusIndex类 if (ignoreGeneratedIndex) { //利用反射来获取订阅类中的订阅方法信息 subscriberMethods = findUsingReflection(subscriberClass); } else { //从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息 subscriberMethods = findUsingInfo(subscriberClass); } //在获得subscriberMethods以后,如果订阅者中不存在@Subscribe注解并且为public的订阅方法,则会抛出异常。 if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //保存进缓存 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } } //METHOD_CACHE,是一个map集合,键是class类型 Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
- 接下来看findUsingInfo(subscriberClass)源码
- 通过getSubscriberInfo方法来获取订阅者信息。在我们开始查找订阅方法的时候并没有忽略注解器为我们生成的索引MyEventBusIndex,如果我们通过EventBusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以得到订阅方法相关的信息,这个时候就不在需要通过注解进行获取订阅方法。如果没有配置MyEventBusIndex,便会执行findUsingReflectionInSingleClass方法,将订阅方法保存到findState中。最后再通过getMethodsAndRelease方法对findState做回收处理并反回订阅方法的List集合。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //获取订阅者信息,没有配置MyEventBusIndex返回null findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { //通过反射来查找订阅方法 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
1.3 查找完所有的订阅方法后便开始对所有的订阅方法进行注册
- 回到register(this)这个方法
image
- 订阅者的注册过程
- 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //获取订阅方法的参数类型 Class<?> eventType = subscriberMethod.eventType; //根据订阅者和订阅方法构造一个订阅事件 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //获取当前订阅事件中Subscription的List集合 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //订阅者已经注册则抛出EventBusException if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //遍历订阅事件,找到比subscriptions中订阅事件小的位置,然后插进去 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; } } //通过订阅者获取该订阅者所订阅事件的集合 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //将当前的订阅事件添加到subscribedEvents中 subscribedEvents.add(eventType); if (subscriberMethod.sticky) { 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); } } }
- 订阅的代码主要就做了两件事,第一件事是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的;typesBySubscriber在调用unregister(this)的时候,根据订阅者找到EventType,又根据EventType找到订阅事件,从而对订阅者进行解绑。第二件事,如果是粘性事件的话,就立马投递、执行。
- 流程图
image
02.EventBus事件分发解析
2.1 从post方法入手
- 首先从PostingThreadState对象中取出事件队列,然后再将当前的事件插入到事件队列当中。最后将队列中的事件依次交由postSingleEvent方法进行处理,并移除该事件。
/** Posts the given event to the event bus. */ public void post(Object event) { //获取当前线程的postingState PostingThreadState postingState = currentPostingThreadState.get(); //取得当前线程的事件队列 List<Object> eventQueue = postingState.eventQueue; //将该事件添加到当前的事件队列中等待分发 eventQueue.add(event); if (!postingState.isPosting) { //判断是否是在主线程post 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; } } }
2.2 什么是PostingThreadState?
- PostingThreadState中包含了当前线程的事件队列,就是当前线程所有分发的事件都保存在eventQueue事件队列中以及订阅者订阅事件等信息,有了这些信息我们就可以从事件队列中取出事件分发给对应的订阅者
final static class PostingThreadState { final List<Object> eventQueue = new ArrayList<Object>();//当前线程的事件队列 boolean isPosting;//是否有事件正在分发 boolean isMainThread;//post的线程是否是主线程 Subscription subscription;//订阅者 Object event;//订阅事件 boolean canceled;//是否取消 }
2.3 PostingThreadState怎么获得?
- ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。
- 可以看出currentPostingThreadState的实现是一个包含了PostingThreadState的ThreadLocal对象,这样可以保证取到的都是自己线程对应的数据。
- 我们有了PostingThreadState获取到了当前线程的事件队列,接下来就是事件分发,我们来看postSingleEvent(eventQueue.remove(0), postingState);
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } };
2.4 来看看postSingleEvent方法里做了什么
- eventInheritance表示是否向上查找事件的父类,它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法
事件分发 private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { //得到事件类型 Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; //是否触发订阅了该事件(eventClass)的父类,以及接口的类的响应方法. 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) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }
2.5 接下来看看postSingleEventForEventType方法
- 同步取出该事件对应的Subscription集合并遍历该集合将事件event和对应Subscription传递给postingState并调用postToSubscription方法对事件进行处理,接下来看看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; }
2.6 接下来看看postToSubscription方法
- 源码如下所示
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING://默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法, //不论该线程是否为主线程(UI 线程)。 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); } }
2.7 整个流程图
image
2.8 总结一下整个事件分发的过程
- 大概如下所示:
- 首先获取当前线程的PostingThreadState对象从而获取到当前线程的事件队列
- 通过事件类型获取到所有订阅者集合
- 通过反射执行订阅者中的订阅方法
03.EventBus取消注册解析
3.1 unregister(this)方法入手
- 代码如下所示
- 获取订阅者的所有订阅的事件类型,然后遍历事件类型的订阅者集合中移除订阅者
/** 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()); } }
- 获取订阅者的所有订阅的事件类型,然后遍历事件类型的订阅者集合中移除订阅者
3.2 再来看看unsubscribeByEventType(subscriber, eventType)
- 获取事件类型的所有订阅者,然后遍历订阅者集合,将解除的订阅者移除
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { //获取事件类型的所有订阅者 List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //遍历订阅者集合,将解除的订阅者移除 if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } }
3.3 取消注册流程图
image
3.4 取消注册的过程
- 总结一下取消注册的过程
- 1、首先获取订阅者的所有订阅事件
- 2、遍历订阅事件
- 3、根据订阅事件获取所有的订阅了该事件的订阅者集合
- 4、将该订阅者移除
- 5、将步骤1中的集合中的订阅者移除
04.总结一下EventBus的工作原理
4.1 订阅逻辑
- 逻辑如下所示:
- 1、首先用register()方法注册一个订阅者
- 2、获取该订阅者的所有订阅的方法
- 3、根据该订阅者的所有订阅的事件类型,将订阅者存入到每个以 事件类型为key 以所有订阅者为values的map集合中
- 4、然后将订阅事件添加到以订阅者为key 以订阅者所有订阅事件为values的map集合中
- 5、如果是订阅了粘滞事件的订阅者,从粘滞事件缓存区获取之前发送过的粘滞事件,响应这些粘滞事件。
4.2 事件发送逻辑
- 逻辑如下所示:
- 1、首先获取当前线程的事件队列
- 2、将要发送的事件添加到事件队列中
- 3、根据发送事件类型获取所有的订阅者
- 4、根据响应方法的执行模式,在相应线程通过反射执行订阅者的订阅方法
4.3 取消逻辑
- 逻辑如下所示:
- 1、首先通过unregister方法拿到要取消的订阅者
- 2、得到该订阅者的所有订阅事件类型
- 3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者
- 4、将订阅者从步骤2的集合中移除