EventBus 流程解析

2024-08-31 01:38
文章标签 流程 解析 eventbus

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

在这里插入图片描述
先介绍控件使用方法,然后再从基本的使用方法断点调试,整体了解一下流程。

EventBus 基本使用

在 module 的 build.gradle添加

implementation 'org.greenrobot:eventbus:3.1.1'

在接收消息的地方注册 eventBus

EventBus.getDefault().register(this);

然后创建一个事件类

public class FirstEvent {public String message;public FirstEvent(String message){this.message = message;}
}

在注册 EventBus 的类中创建接收事件方法

// 这里的threadMode可以设置不同的值,在不同的线程中处理事件接收
@Subscribe(threadMode = ThreadMode.MAIN)
public void setText(FirstEvent event){textView.setText(event.message);
}

然后在任意一个地方发送一个消息

EventBus.getDefault().post(new FirstEvent("来自第某个地方的消息"));

EventBus一个基本的流程就这样完成了,接下来看每一步的源码

注册流程

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

这是一个单例方法,用的是 DoubleCheck 的方式,创建了一个 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);}}
}

简单来看,register 方法是将注册的类与该类中事件处理的方法进行一个绑定。

SubscriberMethod 类是一个实体类,将订阅相关的数据进行一个封装:
image.png

然后我们看一下 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("Subscriber " + subscriberClass+ " and its super classes have no public methods with the @Subscribe annotation");} else {METHOD_CACHE.put(subscriberClass, subscriberMethods);return subscriberMethods;}
}

findSubscriberMethods() 方法会先从 METHOD_CACHE 中查找,没有找到则会根据 ignoreGeneratedIndex 标志选择是采用反射的方式寻找还是用 findUsingInfo 方法,这里这个标志和 findUsingInfo 方法目前都不知道是什么。找到后会将结果加入到缓存中。

接下来就是 subscribe() 方法

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType;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;}}List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);// 后面是粘性事件相关处理,先不管...
}

subscribe 方法主要做两件事,一个是 subscriptionsByEventType.put(eventType, subscriptions) ,subscriptionsByEventType 是以事件的类为 key,订阅者的回调方法为 value 的映射关系表。

另一个是 typesBySubscriber.put(subscriber, subscribedEvents) , typesBySubscriber 是以订阅者为 key, 订阅者订阅的事件类为 value 的映射关系表。

到这里一个 EventBus 的注册流程就结束了,从这个流程来看,主要就是建立事件类和订阅者之间的映射关系。

注销流程

unregister()

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 {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}
}

注销流程就很简单了,先取出该订阅者对应的订阅事件类,然后遍历订阅的事件类,再取出该类所有的订阅者,把我们注销的订阅者移除,最后再把注销的这个订阅者从 typesBySubscriber 中移除。

Post 流程

先看下post的流程图

image.png

然后解析每个步骤,看下都做了哪些操作

post ()

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);}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}
}

PostingThreadState 的实例存储在 ThreadLocal 中,是一个内部静态类,将事件队列,post 状态,事件和订阅者等信息封装在一起。post 方法取出当前线程的PostingThreadState,将事件添加到事件队列中,设置一些状态信息,然后循环把事件队列(用一个 ArrayList 实现)的事件取出来分发。

postSingleEvent()

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();boolean subscriptionFound = false;// 标志位,决定是否要post事件所有父类和实现接口if (eventInheritance) {/* lookupAllEventTypes 方法是找到该事件的所有父类和所有实现的接口*/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) {...// 事件没有订阅者就打打日志啥的}
}

postSingleEvent 方法就两个逻辑,一个是决定是否要post事件所有父类和实现接口,另一个就是事件没有订阅者,会判断一些条件,然后重新 post 一个 NoSubscriberEvent 事件。

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

没啥多说的,根据传过来的事件,拿到对应的订阅关系,PostingThreadState 中记录信息,然后交给 postToSubscription 方法。

postToSubscription ()

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 {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(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);}
}

最后根据订阅者的回调方法设置的线程,决定再什么线程上反射调用该回调方法,反射调用通过 invokeSubscriber 方法。

void invokeSubscriber(Subscription subscription, Object event) {try {subscription.subscriberMethod.method.invoke(subscription.subscriber, event);} catch (InvocationTargetException e) {handleSubscriberException(subscription, event, e.getCause());} catch (IllegalAccessException e) {throw new IllegalStateException("Unexpected exception", e);}
}

这里我们重点关注 EventBus 是如何让事件回调在不同线程中执行的:

  • POSTTING: 不用多说,再哪个线程抛出来的事件,就再哪个线程直接反射调用回调方法,不涉及线程切换
  • MAIN: 在主线程中反射调用回调事件,如果当前线程不是主线程,则通过 mainThreadPoster 插入一个事件, mainThreadPoster 是通过主线程 Looper 创建的一个Handler,调用 enqueue() 方法,会通过自身发一个消息,然后在自己的 handleMessage() 方法中反射调用事件的回调方法,这样就完成了在主线程回调。
  • MAIN_ORDERED: 也是在主线程中反射调用回调方法,但是有顺序,不会发生第二个事件早于第一个事件执行完?不太确定。
  • BACKGROUND: 如果当前线程是主线程,则将事件添加到 backgroundPoster 中,backgroundPoster的 enqueue() 方法会通过线程池执行一个任务,反射调用回调方法。
  • ASYNC: 无论在哪个线程,都交给线程池去处理这个事件。

到这,Post 流程分析基本完成。

这篇关于EventBus 流程解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、