EventBus事件分发框架解读

2024-06-02 00:18

本文主要是介绍EventBus事件分发框架解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

全局框架解析:Trinea的EventBus 源码解析

以下是一些具体过程的分析,最好可以先学习上面的链接内容:

下面的方法是注册最终调用的方法:

 private synchronized void register(Object subscriber, boolean sticky, int priority) {List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod, sticky, priority);}}
1.通过以下方法获取当前订阅者(就是注册的当前类)中所有的事件响应方法(这些响应方法就是该类中的onEvent(),onEventMainThread(),onEventBackgroundThread(),onEventAsync()这些方法),遍历该类中的所有方法,并将这些方法封装到一个集合中,然后以订阅者的类名为key,响应事件的方法集合为value存到一个用作缓存的hashmap中。下次遍历的时候会先通过key获得已经缓存的方法集合,存在就不会重新遍历。(具体参考源码流程)

 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
2.接着循环第一步找到的所有的事件响应方法的集合,并执行subscribe方法:

 for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod, sticky, priority);}
具体方法流程:

 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {Class<?> eventType = subscriberMethod.eventType;CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<Subscription>();subscriptionsByEventType.put(eventType, subscriptions);} else {if (subscriptions.contains(newSubscription)) {throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)// subscriberMethod.method.setAccessible(true);int size = subscriptions.size();for (int i = 0; i <= size; i++) {if (i == size || newSubscription.priority > subscriptions.get(i).priority) {subscriptions.add(i, newSubscription);break;}}List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<Class<?>>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);if (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);}}}
如果通过subscriptionsByEventType.get(eventType)来获取以该事件类型为key的订阅信息集合,如果为null,就会往subscriptionsByEventType的map中添加以事件类型为key,一个存放订阅信息的空集合为value,如下:

 if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<Subscription>();subscriptionsByEventType.put(eventType, subscriptions);}
之后通过一个订阅信息的优先级高低,加入到subscriptions集合中,优先级高的放在集合的前面,比如集合中有三个信息优先级分别为4,2,1;现在进来一个优先级为3的,当i=1的时候,由于3的优先级比2高,所以在集合的第一项中加加入了优先级为3的信息,所以现在里面的顺序应该为4,3,2,1.

 int size = subscriptions.size();for (int i = 0; i <= size; i++) {if (i == size || newSubscription.priority > subscriptions.get(i).priority) {subscriptions.add(i, newSubscription);break;}}

然后通过typesBySubscriber.get(subscriber)来获取以该类(就是订阅者)为key的所有响应事件的集合,如果为null就会往typesBySubscriber的map中添加以订阅者为key,一个存放该类中所有相应事件的空集合为value,然后再把响应事件添加到事件集合中如下:

 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<Class<?>>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);
如果注册的不是sticky事件,就不会进行下面的判断,否则就会进入下面的判断:

if (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);}}
如果注册的是sticky事件,则会试图从stickyEvents的map中获取所有的sticky事件,通过eventType.isAssignableFrom(candidateEventType)判断注册的该事件是否已经在其他类注册过,如果注册过,就会调用下面的方法吧事件post到相应的队列中去或者直接调用(前提是之前已经通过post标记为sticky的事件到stickyEvents中):

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

 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case PostThread:invokeSubscriber(subscription, event);break;case MainThread:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case BackgroundThread: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);}}


PostThread:在post所在的线程直接调用方法,如果在主线程调用post,就是响应事件方法也会在主线程调用,不能执行耗时操作;如果在子线程调用psot,就是响应事件方法也会在子线程调用,这时候不能直接操作主线程中与UI相关的空间,如需要操作UI需要handler或者runOnUiThread(action)。或者使用下面的方法

MainThread:在主线程处理响应事件的方法,如果post在主线程,直接调用事件响应的方法,如果post在子线程,就会先把响应事件通过 mainThreadPoster.enqueue(subscription, event),加入到在HandlerPoster中维护的一个PendingPostQueue队列中:

 void enqueue(Subscription subscription, Object event) {PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost);if (!handlerActive) {handlerActive = true;if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}}}}

由于HandlerPoster是继承Handler,并且绑定了主线程,所以在通过sendMessage(obtainMessage())发送通知,收到通知后就会从PendingPostQueue队列中拿出事件响应方法,并调用:

   @Overridepublic void handleMessage(Message msg) {boolean rescheduled = false;try {long started = SystemClock.uptimeMillis();while (true) {PendingPost pendingPost = queue.poll();if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = queue.poll();if (pendingPost == null) {handlerActive = false;return;}}}eventBus.invokeSubscriber(pendingPost);long timeInMethod = SystemClock.uptimeMillis() - started;if (timeInMethod >= maxMillisInsideHandleMessage) {if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}rescheduled = true;return;}}} finally {handlerActive = rescheduled;}}
BackgroundThread:在子线程中执行事件响应的方法(只有一个子线程,就是加入到子线程中的事件存放在队列中安顺序执行)。如果post在主线程,就通过backgroundPoster.enqueue(subscription, event)加入到在BackgroundPoster中维护的一个PendingPostQueue队列中:

 public void enqueue(Subscription subscription, Object event) {PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost);if (!executorRunning) {executorRunning = true;eventBus.getExecutorService().execute(this);}}}
由于BackgroundPoster是继承Runnable接口的线程对象,然后通过eventBus.getExecutorService().execute(this)将该线程对象加入到线程池中运行,在run方法中就是从队列PendingPostQueue中拿出一个分装了事件响应方法的PendingPost对象,接着通过eventBus.invokeSubscriber(pendingPost)调用事件响应方法,这里通过executorRunning这个标记来辨别前面的事件是否执行完,如果前一个事件还没有执行完毕,又加入了一个事件,则事件先会存放在队列中,run方法在执行完第一个事件的时候接着会从队列中取出第二个方法继续执行,所以就相当于通过eventBus.getExecutorService()创建的newCachedThreadPool线程池中只有一个工作线程在执行当前的线程对象:

 @Overridepublic void run() {try {try {while (true) {PendingPost pendingPost = queue.poll(1000);if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = queue.poll();if (pendingPost == null) {executorRunning = false;return;}}}eventBus.invokeSubscriber(pendingPost);}} catch (InterruptedException e) {Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);}} finally {executorRunning = false;}}
如果post在子线程,则直接通过invokeSubscriber(subscription, event)调用事件响应方法。

Async:在子线程中执行事件响应方法()。通过Executors.newCachedThreadPool()来获得一个线程池:

 public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

然后通过 asyncPoster.enqueue(subscription, event)加入到AsyncPoster维护的一个PendingPostQueue队列中,然后每enqueue一次,就调用 eventBus.getExecutorService().execute(this)来执行将当前线程对象加入到线程池中:

 public void enqueue(Subscription subscription, Object event) {PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);queue.enqueue(pendingPost);eventBus.getExecutorService().execute(this);}

然后通过run方法从队列中拿出事件,并调用:

 @Overridepublic void run() {PendingPost pendingPost = queue.poll();if(pendingPost == null) {throw new IllegalStateException("No pending post available");}eventBus.invokeSubscriber(pendingPost);}

注意:通过registerSticky(Object subscriber)注册前提是在某个类中已经用postSticky(Object event)将某事件提交到stickyEvents中,以至于stickyEvents中存在相同的事件类型,这样才会触发用registerSticky(Object subscriber)注册的类中相应的事件,如果没有提交过则和register(Object subscriber)效果一样。一个类只能注册一次(这里的一个类用内存地址区分)。


其实这个框架是类似于Handler框架。

(一般用于跨线程局部通讯,放在Application的Handler可以实现跨线程全局通信)Handler是初始化在哪个线程,那么消息的处理就在哪个线程,而不去管发送消息的时候在哪个线程,但是handler只适用一对一或者多对一的更新,就是最后只有一处地方可以接受并处理消息;

(全局通信,可以跨进程,线程)Broadcast可以进行全局通信,不管在哪发送,但是接受者一般在主线程执行,只要接受者接受到匹配action的广播,就会处理相应的事件,但是Broadcast可以使用一对多或者多对一的更新,需要在多处跟新数据时可以使用,同时存在可以停留消息的功能。

(跨线程全局通信)EventBus是通过注册类的方法名来确定最后事件处理在哪个线程,也同样不管消息发送在哪个线程,相当于提供了一个全局的handler通信对象,但同时具备了Broadcast消息停留的功能,以及多对一和一对多的更新的功能。

对比:相比之下三者之间的功能Broadcast>EventBus>Handler















这篇关于EventBus事件分发框架解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1022511

相关文章

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

MySQL的ALTER TABLE命令的使用解读

《MySQL的ALTERTABLE命令的使用解读》:本文主要介绍MySQL的ALTERTABLE命令的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、查看所建表的编China编程码格式2、修改表的编码格式3、修改列队数据类型4、添加列5、修改列的位置5.1、把列

Linux CPU飙升排查五步法解读

《LinuxCPU飙升排查五步法解读》:本文主要介绍LinuxCPU飙升排查五步法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录排查思路-五步法1. top命令定位应用进程pid2.php top-Hp[pid]定位应用进程对应的线程tid3. printf"%

C++ HTTP框架推荐(特点及优势)

《C++HTTP框架推荐(特点及优势)》:本文主要介绍C++HTTP框架推荐的相关资料,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Crow2. Drogon3. Pistache4. cpp-httplib5. Beast (Boos

解读@ConfigurationProperties和@value的区别

《解读@ConfigurationProperties和@value的区别》:本文主要介绍@ConfigurationProperties和@value的区别及说明,具有很好的参考价值,希望对大家... 目录1. 功能对比2. 使用场景对比@ConfigurationProperties@Value3. 核

Jupyter notebook安装步骤解读

《Jupyternotebook安装步骤解读》:本文主要介绍Jupyternotebook安装步骤,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、开始安装二、更改打开文件位置和快捷启动方式总结在安装Jupyter notebook 之前,确认您已安装pytho

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Java中的StringUtils.isBlank()方法解读

《Java中的StringUtils.isBlank()方法解读》:本文主要介绍Java中的StringUtils.isBlank()方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录所在库及依赖引入方法签名方法功能示例代码代码解释与其他方法的对比总结StringUtils.isBl

对Django中时区的解读

《对Django中时区的解读》:本文主要介绍对Django中时区的解读方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景前端数据库中存储接口返回AI的解释问题:这样设置的作用答案获取当前时间(自动带时区)转换为北京时间显示总结背景设置时区为北京时间 TIM