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

相关文章

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

MyBatis延迟加载与多级缓存全解析

《MyBatis延迟加载与多级缓存全解析》文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓... 目录MyBATis延迟加载策略一对多示例一对多示例MyBatis框架的缓存一级缓存二级缓存MyBat

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1