【Android】EventBus——进行良好的组件通信

server/2024/12/19 4:55:43/

引言

EventBus是一个基于发布/订阅模式的事件总线库。它主要用于Android应用程序中组件之间的通信,允许不同组件(如ActivityFragmentService等)之间进行松耦合的交互。EventBus通过一个中央事件系统来传递消息,这些消息可以是简单的事件对象,也可以是自定义的事件类。使用EventBus可以使程序开销小,代码更优雅,将发送者与接收者解耦。

使用EventBus

我们先来了解EventBus的三要素与4种ThreadMode

EventBus的三要素:

  • Event:事件,可以是任何类型的对象
  • Subscriber:事件订阅者,在之前消息处理的方式只限于4种线程模型,但之后处理的方法可以随便取名,但是要加上注解@Subscribe,并且要指定线程模型
  • Publisher:事件发布者,可以在任意线程任意位置发送事件,直接调用EventBuspost(Object)方法。可以自己实例化EventBus对象,但一般使用EventBus.getDefault()就可以,根据post函数参数的类型,会自动调用订阅相应类型事件的函数

EventBus的4种ThreadMode(线程模型):

  • POSTING(默认):该事件是在哪个线程发发布出来的,事件处理函数就会在哪个线程中运行,也就是说发送事件和接收事件是在一个线程当中, 因此应该避免进行耗时的操作,因为会阻塞事件的传递,甚至有可能会引起ANR(Application Not Responding)
  • MAIN:事件的处理会在UI线程当中执行。事件处理的事件不能太长,长了会引起ANR问题
  • BACKGROUND:如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行;如果事件本来就是在子线程当中发布出来的,那么该事件处理函数直接在发送事件的线程中执行。在此事件处理函数当中禁止进行UI更新的操作
  • ASYNC:无论事件是在那个线程当中发布的,该事件的处理函数都会在新建的子线程当中运行;同样不能进行UI更新的操作
  • MAIN_ORDERED:处理事件同样会被在UI调用,与MAIN不同在于接收事件是串行的,第二个订阅者需要在第一个订阅者处理完后才会接收到该事件,主要避免堵塞主线程
  1. EventBus的基本用法
  • 定义一个事件类
java">public class MessageEvent {...
}
  • 在需要订阅事件的地方注册事件
java">EventBus.getDefault().register(this);
  • 发送事件
java">EventBus.getDefault().post(messageEvent);
  • 处理事件
java">@Subscribe(threadMode = ThreadMode.MAIN)
public void XXX(MessageEvent1 messageEvent1) {......
}
  • 取消事件订阅
java">EventBus.getDefault().unregister(this);
  1. EventBus应用举例
  • 添加依赖库:
implementation("org.greenrobot:eventbus:3.3.1")
  • 定义消息事件类
java">public class MessageEvent1 {private String message;public MessageEvent1(String message) {this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
  • 注册和取消注册事件
java">@Route(path = "/main/MainActivity")
public class MainActivity extends AppCompatActivity {@Autowired(name = "key1")public String name;@Autowired(name = "key3")public user user;Button button;TextView textView2_receive;@Overrideprotected void onCreate(Bundle savedInstanceState) {......TextView textView = findViewById(R.id.textView1);textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(MainActivity.this, LoginUpActivity.class);startActivity(intent);}});ARouter.getInstance().inject(this);Log.d("TestTest", name + "");if (user != null) {Log.d("TestTest", user.getName() + user.getAge());}button = findViewById(R.id.button_register);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//注册事件EventBus.getDefault().register(MainActivity.this);}});textView2_receive = findViewById(R.id.textView2_receive);}@Overrideprotected void onDestroy() {super.onDestroy();//取消注册事件EventBus.getDefault().unregister(this);}
}
  • 事件订阅者处理事件
java">@Subscribe(threadMode = ThreadMode.MAIN)
public void onMoonEvent(MessageEvent1 messageEvent1) {textView2_receive.setText(messageEvent1.getMessage());
}
  • 事件发布者发送事件
java">button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EventBus.getDefault().post(new MessageEvent1("你接收到了来自登录界面通过EventBus所传递的消息"));/* finish();*/}
});

这样在主活动里面我们先按下注册按钮,跳转到第二个活动按下发送消息,返回到之前的界面会看到消息已经传输过来了。

  • ProGuard混淆视听

最后不要忘了在ProGuard中加入如下混淆视听

java">-keepattributes *Annotation*
-keepclassmembers class ** {@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {<init>(java.lang.Throwable);
}
  1. EventBus的黏性事件

我们要想发送事件之后订阅者仍能接收到该事件,就要用到黏性事件

  • 订阅者处理黏性事件
java">@Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
public void onMoonStickyEvent(MessageEvent1 messageEvent1) {textView2_receive.setText(messageEvent1.getMessage());
}
  • 发送黏性事件
java">button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EventBus.getDefault().postSticky(new MessageEvent1("你接收到了来自登录界面通过EventBus所传递的消息"));/* finish();*/}
});

此时我们先跳转到登录界面,当发送消息之后,返回到主活动再进行注册,此时会发现仍能接收到刚才所发的信息

  • 移除黏性事件,在接收端进行操作,当我们不停创建一个活动的时候,如果不对其进行移除就会出现创建的每一个同样的活动还会接收,但是当我们处理过了仍能接收就很麻烦,因此需要对其进行移除
java">//移除一个
EventBus.getDefault().removeStickyEvent(messageEvent1);
//移除所有
EventBus.getDefault().removeAllStickyEvents();
  • 优先级priority

priority优先级,是一个int类型,默认值为0。值越大,优先级越高,越优先接收到事件。

值得注意的是,只有在post事件和事件接收处理,处于同一个线程环境的时候,才有意义

源码解析EventBus

EventBus的构造方法

我们无论是注册还是发送都要先进行构造,先看看构造方法,其实与ARouter一样,使用的都是双重检查模式

java">public static EventBus getDefault() {EventBus instance = defaultInstance;if (instance == null) {Class var1 = EventBus.class;synchronized(EventBus.class) {instance = defaultInstance;if (instance == null) {instance = defaultInstance = new EventBus();}}}return instance;
}

我们看到返回的单例是一个EventBus,它的构造方法又是什么

java">public EventBus() {this(DEFAULT_BUILDER);
}

DEFAULT_BUILDER是默认的EventBusBuilder,用来构造EventBus

java">private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

this则调用了另一个方法

java">EventBus(EventBusBuilder builder) {this.currentPostingThreadState = new ThreadLocal<PostingThreadState>() {protected PostingThreadState initialValue() {return new PostingThreadState();}};this.logger = builder.getLogger();this.subscriptionsByEventType = new HashMap();this.typesBySubscriber = new HashMap();this.stickyEvents = new ConcurrentHashMap();this.mainThreadSupport = builder.getMainThreadSupport();this.mainThreadPoster = this.mainThreadSupport != null ? this.mainThreadSupport.createPoster(this) : null;this.backgroundPoster = new BackgroundPoster(this);this.asyncPoster = new AsyncPoster(this);this.indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;this.subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex);this.logSubscriberExceptions = builder.logSubscriberExceptions;this.logNoSubscriberMessages = builder.logNoSubscriberMessages;this.sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;this.sendNoSubscriberEvent = builder.sendNoSubscriberEvent;this.throwSubscriberException = builder.throwSubscriberException;this.eventInheritance = builder.eventInheritance;this.executorService = builder.executorService;
}

通过使用建造者模式进行配置

订阅者注册

获取到之后先来看看注册者的方法

java">public void register(Object subscriber) {if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {throw new RuntimeException("It looks like you are using EventBus on Android, make sure to add the \"eventbus\" Android library to your dependencies.");} else {Class<?> subscriberClass = subscriber.getClass();List<SubscriberMethod> subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriberClass);//1synchronized(this) {Iterator var5 = subscriberMethods.iterator();while(var5.hasNext()) {SubscriberMethod subscriberMethod = (SubscriberMethod)var5.next();this.subscribe(subscriber, subscriberMethod);//2}}}
}

这段代码干了三件事:

检查 Android 依赖:首先检查是否在 Android 环境中运行,并且是否缺少必要的 Android 组件。如果检测到这种情况,会抛出一个 RuntimeException,提示用户需要在项目的依赖中添加 EventBus 的 Android 库。这是因为 EventBus 的核心库可能不包含所有 Android 特定的类和资源,需要额外的 Android 库来支持。

获取订阅者类和查找订阅方法:获取订阅者对象的类类型,并使用 subscriberMethodFinder 查找这个类中所有标记有 @Subscribe 注解的方法。这些方法将作为事件的订阅点。

订阅方法注册:这个同步块确保在多线程环境中,对订阅者方法的注册是线程安全的。使用迭代器遍历所有找到的订阅方法,对于每个订阅方法,调用 subscribe 方法将订阅者和订阅方法注册到 EventBus 中。subscribe 方法会将订阅者和对应的方法添加到内部的数据结构中,以便在事件发生时能够调用正确的方法。

  • 查找订阅者的订阅方法

    先看上面注释1处的代码,使用findSubscriberMethods方法找到一个SubscriberMethod的集合,也就是传进来的订阅者的所有订阅方法,接下来遍历订阅者的订阅方法来完成订阅者的注册操作。

在这里插入图片描述

SubscriberMethod主要用来保存订阅方法中的Method对象、线程模式、事件类型、优先级、是否为黏性事件等属性。

看看方法的具体实现:

java">List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {//从 METHOD_CACHE 缓存中获取与给定的 subscriberClass 相关联的订阅者方法列表 subscriberMethods。METHOD_CACHE 是一个缓存,用于存储类到其订阅者方法的映射,以提高性能。List<SubscriberMethod> subscriberMethods = (List)METHOD_CACHE.get(subscriberClass);//1if (subscriberMethods != null) {return subscriberMethods;} else {//如果 ignoreGeneratedIndex 配置为 true,则使用反射来查找订阅者方法(findUsingReflection 方法)。if (this.ignoreGeneratedIndex) {subscriberMethods = this.findUsingReflection(subscriberClass);} else {//使用预先生成的索引来查找订阅者方法(findUsingInfo 方法)。subscriberMethods = this.findUsingInfo(subscriberClass);//3}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);//2return subscriberMethods;}}
}

注释1处是从缓存当中查找是否有订阅方法的集合,如果找到了就直接返回,如果没有就开始查找,根据ignoreGeneratedIndex属性选择以什么方式进行查找,这个属性就代表了是否忽略注解器生成的MyEventBusIndex

MyEventBusIndex 是一个由 EventBus 自动生成的类,用于在编译时通过注解处理器生成索引,加速事件订阅者的注册过程。这个类实现了 SubscriberInfoIndex 接口,包含了一个静态的 Map,该 Map 存储了所有订阅者类与其对应的 SubscriberInfo 对象。

ignoreGeneratedIndex的默认值为false,可以通过EventBusBuilder来设置它的值。上面提到会先在缓存当中寻找,到底在什么时候会将其放在缓存当中,看看注释2,当我们找到订阅方法的集合之后就将其放到缓存当中,方便下一次直接使用缓存当中的。我们一般会使用默认false的情况,即执行的是注释3处的代码:

java">private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {//创建并初始化了一个 FindState 对象,用于记录查找过程中的状态,包括当前处理的类、订阅者信息等。FindState findState = this.prepareFindState();//将FindState对象设置为处理传入的subscriberClass类findState.initForSubscriber(subscriberClass);for(; findState.clazz != null; findState.moveToSuperclass()) {findState.subscriberInfo = this.getSubscriberInfo(findState);//1从索引中获取当前类的 SubscriberInfo 对象,该对象包含了类中所有订阅方法的信息。if (findState.subscriberInfo != null) {//如果找到了 SubscriberInfo 对象,获取其中的订阅方法数组。遍历这些订阅方法,对于每个方法,使用 findState.checkAdd 方法检查是否应该添加到结果列表中。如果检查通过,将该方法添加到 findState.subscriberMethods 列表中。//从预先生成的索引中获取当前类的 SubscriberInfo 对象,该对象包含了类中所有订阅方法的信息。SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();//2SubscriberMethod[] var4 = array;int var5 = array.length;//如果找到了 SubscriberInfo 对象,获取其中的订阅方法数组,并遍历这些订阅方法。for(int var6 = 0; var6 < var5; ++var6) {SubscriberMethod subscriberMethod = var4[var6];if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {//对于每个订阅方法,使用 findState.checkAdd 方法检查是否应该添加到结果列表中。如果检查通过,将该方法添加到 findState.subscriberMethods 列表中findState.subscriberMethods.add(subscriberMethod);}}} else {//如果没有找到 SubscriberInfo 对象,使用反射来在单个类中查找订阅者方法(findUsingReflectionInSingleClass 方法)this.findUsingReflectionInSingleClass(findState);//3}}return this.getMethodsAndRelease(findState);
}

在注释1处我们通过getSubscriberInfo方法获取到订阅者的信息。在我们开始查找订阅者方法的时候并没忽略注解器为我们生成的索引MyEventIndex。如果我们通过EventBusBuilder配置了MyEventIndex,便会得到subscriberInfo

在注释2的地方,得到订阅方法的相关信息,如果没有配置EventBusIndex便会执行注释3处的代码。

在注释3处调用findUsingReflectionInSingleClass方法,将订阅方法保存到findState当中、

最后通过getMethodsAndRelease方法对findState做回收处理并返回订阅方法的List集合,由于我们在一般注册的时候并没有设置EventBusIndex,因此一般执行的都是注释3处的代码,接下来就看看这里的代码:

java">private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {methods = findState.clazz.getDeclaredMethods();//1} catch (Throwable var13) {......}Method[] var3 = methods;int var4 = methods.length;//遍历一个类中的所有方法,并检查这些方法是否符合作为事件订阅者方法的条件for(int var14 = 0; var14 < var4; ++var14) {Method method = var3[var14];int modifiers = method.getModifiers();if ((modifiers & 1) != 0 && (modifiers & 5192) == 0) {Class<?>[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {Subscribe subscribeAnnotation = (Subscribe)method.getAnnotation(Subscribe.class);if (subscribeAnnotation != null) {Class<?> eventType = parameterTypes[0];if (findState.checkAdd(method, eventType)) {ThreadMode threadMode = subscribeAnnotation.threadMode();findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky()));}}} else if (this.strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length);}} else if (this.strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract");}}}

在上面注释1的地方就通过反射来获取订阅者中所有的方法,并根据方法的类型、参数和注解来找到订阅方法。找到之后将其信息保存到findState中。

挺复杂的就对上面的代码做一个简单的解释吧,上面就是我们要对方法进行注册就要先让其找到所要注册的方法,即对得到返回信息的处理方法。代码会先在缓存区看是否能找到之前缓存的订阅方法,直接将其返回,但是若没有我们就要开始寻找订阅者的订阅方法,进一步去寻找所要注册的方法,则先获取订阅者的所有方法,根据各个属性以及注解筛选所需注册的订阅方法,这样就获取到了。这时就会提到为什么之前我们可以在缓存区获取到,是因为之前没有注册过因此没有信息,此时获取之后就会放到缓存区方便下一次获取。

  • 订阅者的注册方法

上面已经获取到所有的订阅方法就应该进行注册了,再返回到一开始的register方法当中,在注释2subscribe方法当中对订阅方法进行注册,注意是通过循环将一个个方法进行注册的

java">//传进来的即为订阅者(class)和订阅方法(method)
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {//subscriberMethod 是一个对象,它包含了关于一个订阅者方法的信息,比如这个方法所属的类、方法名、参数类型等。subscriberMethod.eventType 是 subscriberMethod 对象的一个属性,它指定了该订阅方法能够处理的事件类型(本质是一个class,就是我们传进来的方法作为了class)。Class<?> eventType = subscriberMethod.eventType;Subscription newSubscription = new Subscription(subscriber, subscriberMethod);//1//尝试从 subscriptionsByEventType 映射中获取对应事件类型的订阅列表(映射(Map)的目的是快速地根据事件类型查找到所有注册了该事件类型的订阅者)CopyOnWriteArrayList<Subscription> subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(eventType);//2if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList();this.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 > ((Subscription)subscriptions.get(i)).subscriberMethod.priority) {subscriptions.add(i, newSubscription);//3break;}}//试图从 typesBySubscriber 映射中检索与给定的 subscriber 相关联的值,即订阅者订阅的所有事件类型的列表List<Class<?>> subscribedEvents = (List)this.typesBySubscriber.get(subscriber);//4if (subscribedEvents == null) {subscribedEvents = new ArrayList();this.typesBySubscriber.put(subscriber, subscribedEvents);}((List)subscribedEvents).add(eventType);if (subscriberMethod.sticky) {//eventInheritance 是 EventBus 框架中的一个配置选项,用于控制事件发布时是否考虑事件类型的继承关系。具体来说,这个选项决定了 EventBus 在处理事件时是否将事件的父类或接口也视为有效的事件类型进行分发if (this.eventInheritance) {//黏性事件的处理Set<Map.Entry<Class<?>, Object>> entries = this.stickyEvents.entrySet();Iterator var9 = entries.iterator();while(var9.hasNext()) {Map.Entry<Class<?>, Object> entry = (Map.Entry)var9.next();Class<?> candidateEventType = (Class)entry.getKey();if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();this.checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = this.stickyEvents.get(eventType);this.checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}

注释1处通过subscriber(订阅者)与subscriberMethod(订阅方法)创建一个Subscription(订阅对象)。

在这里插入图片描述

注释2处根据eventType(事件类型)获取到订阅对象集合。若果为null就重新进行创建并将Subscription根据eventType保存在subscriptionsByEventType(Map集合)当中。

这行代码的作用是从 EventBus 的内部映射中检索与特定事件类型相关联的订阅者列表,并将其存储在 subscriptions 变量中。如果这个列表还不存在,subscriptions 将为 null,这时 EventBus 需要创建一个新的 CopyOnWriteArrayList 并将其添加到映射中。如果列表已存在,subscriptions 将包含所有已经订阅了该事件类型的订阅者。

注释3处按照订阅方法的优先级插入到订阅对象集合中,完成订阅方法的注册。

注释4通过subscriber获取subscribedEvents(事件类型集合),如果为空则重新进行创建,并将eventType添加到subscribedEvents当中,并根据subscribersubscribedEvents存储在typeBySubscriber(Map集合)。

在这里插入图片描述

当这个方法为黏性事件,则从stickyEvents事件保存队列中取出该事件类型的事件发送给当前订阅者。

这部分做了两件事情:

  1. Subscriptions根据EventType封装到subscriptionsByEventType中,将subscribedEventssubscribedEvents 是 EventBus 中的一个数据结构,它用于记录一个订阅者(subscriber)订阅了哪些事件类型。)根据subscriber封装到typesBySubscriber

subscriptionsByEventType

  • 目的subscriptionsByEventType 用于将每个事件类型映射到一个订阅者列表。这个列表中的每个元素都是 Subscription 对象,代表一个订阅者对特定事件类型的兴趣。
  • 封装过程
    • 当一个订阅者(subscriber)订阅了一个事件类型(eventType),EventBus 会创建一个 Subscription 对象,它包含了订阅者对象和订阅方法(SubscriberMethod)。
    • EventBus 会查找 subscriptionsByEventType 中是否存在该事件类型的键。如果不存在,它会创建一个新的 CopyOnWriteArrayList 并将其与事件类型关联。
    • 然后,EventBus 将新的 Subscription 对象添加到对应事件类型的列表中。
  • 结果:这样,当一个事件被发布时,EventBus 可以通过事件类型快速找到所有订阅了该事件类型的订阅者,并触发相应的订阅方法。

typesBySubscriber

  • 目的typesBySubscriber 用于将每个订阅者映射到一个事件类型列表。这个列表包含了订阅者订阅的所有事件类型。
  • 封装过程
    • 当一个订阅者订阅了一个或多个事件类型时,EventBus 会查找 typesBySubscriber 中是否存在该订阅者的键。如果不存在,它会创建一个新的 List 并将其与订阅者对象关联。
    • 然后,EventBus 将新的事件类型添加到对应订阅者的列表中。
  1. 对粘性事件的处理

总结:

事件的发送

我们获取到EventBus对象之后就可以根据post方法进行提交

java">public void post(Object event) {//从 currentPostingThreadState 原子引用中获取当前线程的 PostingThreadState 对象。PostingThreadState保存着事件队列和线程状态信息PostingThreadState postingState = (PostingThreadState)this.currentPostingThreadState.get();//获取事件队列,并将当前事件插入事件队列List<Object> eventQueue = postingState.eventQueue;eventQueue.add(event);//如果当前线程的 isPosting 标志为 false,表示当前线程还没有开始发布事件if (!postingState.isPosting) {//设置 isPosting 标志为 true,表示开始发布事件。同时,设置 isMainThread 标志,表示是否在主线程发布事件。postingState.isMainThread = this.isMainThread();postingState.isPosting = true;//如果 PostingThreadState 的 canceled 标志为 true,表示之前有发布操作被取消,抛出异常if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {//使用 while 循环处理事件队列中的所有事件。每次循环调用 postSingleEvent 方法处理队列中的第一个事件,并将其从队列中移除。while(!eventQueue.isEmpty()) {this.postSingleEvent(eventQueue.remove(0), postingState);}} finally {//设置 isPosting 标志为 false,表示完成事件发布。同时,将 isMainThread 标志设置为 falsepostingState.isPosting = false;postingState.isMainThread = false;}}
}

postingState.isMainThread = false的作用:

  • 重置状态:无论事件发布过程是否成功,都需要确保 isMainThread 标志被重置,以反映当前线程的实际状态,为下一次事件发布准备正确的上下文信息。
  • 避免状态污染:如果在事件发布过程中发生异常或其他问题,没有正确重置 isMainThread 可能会导致后续事件发布错误地认为它们发生在主线程上,从而影响事件的正确分发和处理。

我们看到最终使用的是postSingleEvent方法进行事件的发布

java">private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();//这个布尔变量用于跟踪是否找到了匹配的订阅者boolean subscriptionFound = false;//eventInheritance表示是否向上查找事件的父类,默认为true,可以使用EventBusBuilder进行配置if (this.eventInheritance) {//如果 EventBus 配置为考虑事件继承(eventInheritance 为 true),则查找事件类及其所有父类和接口,将它们作为可能的事件类型。List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();for(int h = 0; h < countTypes; ++h) {//遍历所有事件类型,并对每种类型调用 postSingleEventForEventType 方法。|= 运算符用于累加订阅者是否找到的标志。Class<?> clazz = (Class)eventTypes.get(h);subscriptionFound |= this.postSingleEventForEventType(event, postingState, clazz);}} else {//如果不考虑事件继承,直接使用事件对象的类类型调用 postSingleEventForEventType 方法。subscriptionFound = this.postSingleEventForEventType(event, postingState, eventClass);}//找不到该事件的异常处理if (!subscriptionFound) {//如果没有找到任何订阅者,且配置为记录无订阅者消息(logNoSubscriberMessages 为 true),则记录一条日志消息。if (this.logNoSubscriberMessages) {this.logger.log(Level.FINE, "No subscribers registered for event " + eventClass);}//如果没有找到任何订阅者,且配置为发送无订阅者事件(sendNoSubscriberEvent 为 true),且事件类型不是 NoSubscriberEvent 或 SubscriberExceptionEvent,则发布一个 NoSubscriberEvent 事件,通知监听无订阅者事件的订阅者。if (this.sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {this.post(new NoSubscriberEvent(this, event));}}
}

eventInheritance为true的时候则通过lookupAllEventTypes找到所有的父类事件并存在List当中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法。

java">private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {//这段代码在同步块中获取与事件类型 eventClass 相关联的订阅者列表 subscriptions。使用 synchronized 确保线程安全,避免在多线程环境下发生并发修改异常。CopyOnWriteArrayList subscriptions;synchronized(this) {subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {Iterator var5 = subscriptions.iterator();while(var5.hasNext()) {Subscription subscription = (Subscription)var5.next();//在 PostingThreadState 中设置当前事件和订阅关系,以便在发布过程中跟踪状态。postingState.event = event;postingState.subscription = subscription;boolean aborted;try {//调用 postToSubscription 方法将事件发布给当前订阅者。this.postToSubscription(subscription, event, postingState.isMainThread);aborted = postingState.canceled;} finally {//在 try-finally 块中,无论发布成功与否,都清理 PostingThreadState,重置事件和订阅关系,并清除取消标志postingState.event = null;postingState.subscription = null;postingState.canceled = false;}if (aborted) {break;}}return true;} else {return false;}
}

接下来看看postToSubscription方法:

java">//
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {//据订阅方法中指定的线程模式(ThreadMode)来决定如何处理事件的投递switch (subscription.subscriberMethod.threadMode) {//如果线程模式是 POSTING,则直接在当前线程调用订阅者的方法(invokeSubscriber)。case POSTING:this.invokeSubscriber(subscription, event);break;//如果线程模式是 MAIN,并且当前是主线程(isMainThread 为 true),则直接调用订阅者的方法//如果不在主线程,则将事件和订阅者信息加入主线程队列(mainThreadPoster),以便稍后在主线程中处理case MAIN:if (isMainThread) {this.invokeSubscriber(subscription, event);} else {this.mainThreadPoster.enqueue(subscription, event);}break;//如果线程模式是 MAIN_ORDERED,并且 mainThreadPoster 不为 null,则将事件和订阅者信息加入主线程队列。//如果 mainThreadPoster 为 null,则直接调用订阅者的方法。                      case MAIN_ORDERED:if (this.mainThreadPoster != null) {this.mainThreadPoster.enqueue(subscription, event);} else {this.invokeSubscriber(subscription, event);}break;//如果线程模式是 BACKGROUND,并且在主线程,则将事件和订阅者信息加入后台线程队列(backgroundPoster)。//如果不在主线程,则直接调用订阅者的方法。case BACKGROUND:if (isMainThread) {this.backgroundPoster.enqueue(subscription, event);} else {this.invokeSubscriber(subscription, event);}break;//如果线程模式是 ASYNC,则将事件和订阅者信息加入异步队列(asyncPoster),以便在另一个线程中异步处理case ASYNC:this.asyncPoster.enqueue(subscription, event);break;default:throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);}
}

mainThreadPosterHanderPoster类型的,继承自Handler,通过Handler将订阅方法切换到主线程执行

订阅者取消注册

取消注册则需要调用unregister方法:

java">//unregister 方法被声明为 synchronized,这意味着在同一时间只能有一个线程执行这个方法。这是为了确保在多线程环境下对 EventBus 进行修改时的线程安全
public synchronized void unregister(Object subscriber) {//从 typesBySubscriber 映射中获取与订阅者对象 subscriber 相关联的事件类型列表 subscribedTypesList<Class<?>> subscribedTypes = (List)this.typesBySubscriber.get(subscriber);if (subscribedTypes != null) {Iterator var3 = subscribedTypes.iterator();//对于每个事件类型,调用 unsubscribeByEventType 方法从 subscriptionsByEventType 映射中移除订阅者。while(var3.hasNext()) {Class<?> eventType = (Class)var3.next();this.unsubscribeByEventType(subscriber, eventType);}//注销完成后,从 typesBySubscriber 映射中移除订阅者对象,表示该订阅者不再订阅任何事件。this.typesBySubscriber.remove(subscriber);} else {this.logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}
}

调用 unsubscribeByEventType 方法从 subscriptionsByEventType 映射中移除订阅者:

java">private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {//从 subscriptionsByEventType 映射中获取与事件类型 eventType 相关联的订阅列表 subscriptions。List<Subscription> subscriptions = (List)this.subscriptionsByEventType.get(eventType);if (subscriptions != null) {int size = subscriptions.size();for(int i = 0; i < size; ++i) {Subscription subscription = (Subscription)subscriptions.get(i);//如果当前 Subscription 对象的 subscriber 属性与传入的 subscriber 参数匹配,表示找到了要注销的订阅者。if (subscription.subscriber == subscriber) {//将匹配的 Subscription 对象标记为非活跃状态(active = false),表示这个订阅者不再接收事件。subscription.active = false;//从 subscriptions 列表中移除这个 Subscription 对象。subscriptions.remove(i);--i;--size;}}}
}

文章到这里就结束了!

感谢阅读


http://www.ppmy.cn/server/151360.html

相关文章

14篇--模板匹配

原理 模板匹配就是用模板图&#xff08;通常是一个小图&#xff09;在目标图像&#xff08;通常是一个比模板图大的图片&#xff09;中不断的滑动比较&#xff0c;通过某种比较方法来判断是否匹配成功。 匹配方法 1. 平方差匹配TM_SQDIFF 以模板图与目标图所对应的像素值使用…

概率论得学习和整理25:EXCEL 关于直方图/ 频度图 /hist图的细节,2种做hist图的方法

目录 1 hist图的特点 2 hist的设置技巧&#xff1a;直接生成的hist图往往很奇怪不好用&#xff1a;因为横轴的分组不对 3 如何修改分组 4 设置开放边界&#xff0c;把长尾合并&#xff0c;得到hist图1 5 用原始表得到频数表 6 用上面的频数图做柱状图&#xff0c;再修改&…

蓝桥杯刷题——day4

蓝桥杯刷题——day4 题目一题干题目解析代码 题目二题干题目解析代码 题目一 题干 小蓝和朋友们在玩一个报数游戏。由于今年是2024 年&#xff0c;他们决定要从小到大轮流报出是20或24倍数的正整数。前10个被报出的数是&#xff1a;20,24,40,48,60,72,80,96,100,120。请问第2…

《探秘开源气味数据库:数字世界里的“气味宝藏”》

《探秘开源气味数据库&#xff1a;数字世界里的“气味宝藏”》 一、开源气味数据库的兴起背景&#xff08;一&#xff09;技术发展的推动&#xff08;二&#xff09;市场需求的催生 二、常见的开源气味数据库介绍&#xff08;一&#xff09;GS-LF 香精香料数据库&#xff08;二…

Htpp中web通讯发送post(上传文件)、get请求

一、正常发送post请求 1、引入pom文件 <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5</version></dependency>2、这个是发送至正常的post、get请求 import org…

LVGL9.2 鼠标悬停处理

文章目录 前言鼠标悬停处理功能简介使用场景1. **按钮悬停效果**2. **图标高亮**3. **动态工具提示** 实现原理使用方法1. 设置控件样式2. 监听悬停事件 功能亮点注意事项总结 前言 在 v9.2 版本中&#xff0c;新增了许多功能&#xff0c;其中鼠标悬停处理&#xff08;Mouse H…

React 基础:剖析 UI 描述之道

目录 介绍编写你的第一个组件定义组件使用组件组件的导入与导出 使用JSX代替HTMLJSX规则JSX中通过大括号使用JS 将 Props 传递给组件向组件传递props使用 JSX 展开语法传递 props将JSX作为子组件传递 条件渲染条件返回JSX三目运算符与运算符 渲染列表对数组进行过滤并渲染数据用…

Linux高性能服务器编程中的TCP带外数据梳理总结

Linux高性能服务器编程中的TCP带外数据梳理总结 文章目录 Linux高性能服务器编程中的TCP带外数据梳理总结1.TCP 带外数据总结2.第五章带外数据send.crecv.c 3.第九章带外数据send.cselect.c 4.第十章带外数据send.csig_msg.c 1.TCP 带外数据总结 至此&#xff0c;我们讨论完了…