简介:在本文中,我们将详细介绍如何从头开始实现一个轻量级的 EventBus 库。我们将以 XEventBus 为例,阐述整个实现过程,以及在实现过程中遇到的关键问题和解决方法。
XEventBus 地址:https://github.com/LucasXu01/XBus
EventBus 是一个基于发布/订阅模式的事件总线库,用于在组件之间进行解耦的异步通信。它允许组件(如 Activity、Fragment、Service 等)之间相互发送和接收事件,而无需显式地引用和调用彼此的实例。这样,组件之间的耦合性降低,代码更易于维护和扩展。
为了学习和掌握 EventBus 的实现原理和关键技术,我们将通过本文带领大家从零开始实现一个自己的轻量级的事件总线库 XBus。
XBus 是一个轻量级的事件总线库,旨在提供简单、高效、易用的事件通信机制。我们的设计目标如下:
在这一节中,我们将结合代码详细介绍 XEventBus 的设计与实现。
XEventBus 的实现思路包括以下几个方面:
通过这种设计,XEventBus 实现了一个简单、高效、易用的事件总线,可以方便地在不同组件之间进行事件通信。
XEventBus 类是该库的核心类,它维护了一个 Map
结构,用于存储订阅者和它们对应的订阅方法列表。通过 register 和 unregister 方法,实现订阅和取消订阅的功能。
public class XEventBus {
private static final Map<Class<?>, List<Subscription>> subscriptionsByEventType = new ConcurrentHashMap<>();
public void register(Object subscriber) {
//...
}
public void unregister(Object subscriber) {
//...
}
public void post(Object event) {
//...
}
}
SubscribedMethod 类封装了订阅者方法的相关信息,包括所在类、参数类型、线程模式、优先级和方法名。在事件发布时,EventBus 将根据这些信息找到并调用订阅者方法。
public class SubscribedMethod {
private final Class<?> subscriberClass;
private final Class<?> eventType;
private final ThreadMode threadMode;
private final int priority;
private final String methodName;
//... constructor and getters
}
Subscription 类表示一个订阅关系,包括订阅者对象(Subscriber)和订阅方法(SubscribedMethod)。当事件发布时,EventBus 会遍历所有的 Subscription,根据事件类型找到匹配的订阅者方法并执行。
public class Subscription {
private final Object subscriber;
private final SubscribedMethod subscribedMethod;
//... constructor and getters
}
Subscriber 类表示订阅者对象,包含了订阅者实例和其订阅的事件方法。Subscriber 类主要用于在 EventBus 内部管理订阅关系。
public class Subscriber {
private final Object subscriberInstance;
private final List<SubscribedMethod> subscribedMethods;
//... constructor and getters
}
MethodHandle 接口用于处理订阅者方法的查找和调用。通过注解处理器(APT)生成具体的实现类,实现在编译期间就能获取订阅者方法信息,提高运行时性能。
public interface MethodHandle {
List<SubscribedMethod> getAllSubscribedMethods(Object subscriber);
void invokeMethod(Subscription subscription, Object event);
}
在 XEventBus 的实现中,注解处理器 MyEventBusAnnotationProcessor 负责在编译期间查找所有使用 @Subscribe 注解的方法,并生成 AptMethodFinder 类,实现 MethodHandle 接口。这样,在运行时,XEventBus 无需使用反射,可以直接调用订阅者方法,提高性能。
// MyEventBusAnnotationProcessor
public class MyEventBusAnnotationProcessor extends AbstractProcessor {
//... processing logic
}
// Generated AptMethodFinder
public class AptMethodFinder implements MethodHandle {
//... implementation
}
通过上述代码实现,XEventBus 可以在不同组件之间进行高效的事件通信。在编译期间,MyEventBusAnnotationProcessor 注解处理器会自动生成 AptMethodFinder 类,实现了 MethodHandle 接口。这使得 XEventBus 在运行时能够直接调用订阅者方法,而不需要使用反射,从而提高了性能。
为了实现 XEventBus 中订阅者方法的查找与注册,我们需要使用注解处理器(APT)在编译期间生成相应的代码。这将避免在运行时使用反射,从而提高性能。
我们在 MyEventBusAnnotationProcessor 注解处理器中遍历所有使用 @Subscribe 注解的方法,将它们按照所属类进行分类。接着,为每个类生成一个查找订阅者方法的静态方法,该方法返回一个包含所有订阅者方法信息的 SubscribedMethod 列表。
for (Element element : elements) {
// 省略代码...
CreateMethod createMethod = mCachedCreateMethod.get(qualifiedName);
if (createMethod == null) {
createMethod = new CreateMethod(typeElement);
mCachedCreateMethod.put(qualifiedName, createMethod);
}
// 省略代码...
}
在 EventBus 类中,我们提供了 register() 方法,用于将订阅者方法注册到事件总线中。注册过程中,EventBus 会调用 AptMethodFinder 类中的 getAllSubscribedMethods() 方法,获取订阅者类中所有订阅者方法的信息,并将它们封装成 Subscription 对象,存储在事件总线的内部数据结构中。
public void register(Object subscriber) {
List<SubscribedMethod> subscribedMethods = methodHandle.getAllSubscribedMethods(subscriber);
// 省略代码...
}
在 MyEventBusAnnotationProcessor 注解处理器中,我们为每个订阅者类生成一个查找订阅者方法的静态方法,该方法返回一个包含所有订阅者方法信息的 SubscribedMethod 列表。同时,我们还需要为 AptMethodFinder 类生成一个 invokeMethod() 方法,用于执行具体的订阅者方法。
这样,在 EventBus 发布事件时,可以直接通过 AptMethodFinder 的 invokeMethod() 方法调用订阅者方法,而无需使用反射。
通过以上步骤,我们实现了订阅者方法的查找与注册,从而使 XEventBus 能够在不同组件之间高效地传递事件。
在 XEventBus 中,我们实现了事件的发布与订阅者方法的调用。接下来,我们将讨论这些关键功能的实现细节。
在 EventBus 类中,我们提供了 post() 方法,用于发布事件。当调用此方法时,EventBus 将遍历内部数据结构中的所有订阅者,并根据事件类型找到对应的订阅者方法。
public void post(Object event) {
List<Subscription> subscriptions = mEventTypeSubscriptions.get(event.getClass());
if (subscriptions != null) {
// 省略代码...
}
}
为了避免使用反射调用订阅者方法,我们使用 MethodHandle 接口,它允许我们在编译期生成的代码中直接调用订阅者方法。在 EventBus 类中,我们将 MethodHandle 作为一个成员变量,并在构造函数中初始化。
public EventBus() {
this.methodHandle = new AptMethodFinder();
}
当我们找到订阅者方法时,我们使用 MethodHandle 接口的 invokeMethod() 方法来调用它。这样,我们可以避免使用反射,从而提高性能。
private void invokeSubscriber(Subscription subscription, Object event) {
try {
methodHandle.invokeMethod(subscription.subscriber, subscription.subscribedMethod, event);
} catch (Exception e) {
// 省略代码...
}
}
XEventBus 支持多种线程模型,如主线程、后台线程等。我们可以在 @Subscribe 注解中指定线程模型。为了实现这一功能,我们在 Subscription 类中保存订阅者方法的线程模型,并在调用订阅者方法时根据线程模型执行相应的操作。
private void invokeSubscriber(Subscription subscription, Object event) {
ThreadMode threadMode = subscription.subscribedMethod.threadMode;
switch (threadMode) {
case MAIN:
// 省略代码...
break;
case BACKGROUND:
// 省略代码...
break;
// 更多线程模型...
}
}
通过以上实现,我们使 XEventBus 能够根据订阅者方法的线程模型在不同线程中调用订阅者方法,从而实现了灵活的事件发布与订阅。
为了避免内存泄漏和不必要的事件接收,我们需要提供反注册功能,以便在不再需要接收事件的时候移除订阅者方法。以下是反注册功能的实现细节。
我们在 EventBus 类中提供了一个名为 unregister() 的方法,用于移除订阅者方法。在调用此方法时,我们将遍历订阅者方法并从内部数据结构中删除它们。
public void unregister(Object subscriber) {
List<Class<?>> subscribedEventTypes = mSubscriberEventTypes.get(subscriber);
if (subscribedEventTypes != null) {
for (Class<?> eventType : subscribedEventTypes) {
removeSubscriber(subscriber, eventType);
}
}
}
private void removeSubscriber(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = mEventTypeSubscriptions.get(eventType);
if (subscriptions != null) {
// 省略代码...
}
}
在反注册订阅者方法时,我们需要确保清理相关的资源。首先,我们从 mSubscriberEventTypes 中删除订阅者。然后,我们检查事件类型是否还有其他订阅者,如果没有,则从 mEventTypeSubscriptions 中删除该事件类型。
private void removeSubscriber(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = mEventTypeSubscriptions.get(eventType);
if (subscriptions != null) {
Iterator<Subscription> iterator = subscriptions.iterator();
while (iterator.hasNext()) {
Subscription subscription = iterator.next();
if (subscription.subscriber == subscriber) {
iterator.remove();
}
}
// 清理资源
if (subscriptions.isEmpty()) {
mEventTypeSubscriptions.remove(eventType);
}
}
mSubscriberEventTypes.remove(subscriber);
}
通过实现反注册功能,我们使得 XEventBus 可以灵活地处理订阅者的生命周期,避免了潜在的内存泄漏问题。同时,这也有助于提高事件分发的性能,因为我们不再需要为不再关心的事件处理订阅者方法。
为了提高 XEventBus 的性能和灵活性,我们可以考虑以下几个方面:
在 XEventBus 中,我们可以使用缓存策略来减少重复的订阅者方法查找和事件发布。例如,我们可以使用一个 HashMap 来存储已经注册过的订阅者方法,以便在需要时快速查找。同时,在事件发布时,我们可以对订阅者方法的调用结果进行缓存,避免重复计算。
private static final Map<Class<?>, List<SubscribedMethod>> METHOD_CACHE = new HashMap<>();
在某些场景下,我们可能需要对订阅者方法的执行顺序进行控制。为此,我们可以为订阅者方法添加优先级属性。通过在 @Subscribe 注解中添加 priority 属性,我们可以实现订阅者方法的优先级控制。
@Subscribe(priority = 1)
public void onEvent(Event event) {
// ...
}
此外,我们还可以通过添加 delay 属性来实现延时处理。例如,当 delay 设为 1000 时,订阅者方法将在事件发布后的 1000 毫秒后执行。
@Subscribe(delay = 1000)
public void onEvent(Event event) {
// ...
}
除了以上提到的优化,我们还可以为 XEventBus 添加更多功能,如支持粘性事件。粘性事件是指在订阅者注册后,仍然可以接收到在注册之前发布的事件。我们可以通过在 @Subscribe 注解中添加一个 sticky 属性来实现这个功能。
@Subscribe(sticky = true)
public void onStickyEvent(Event event) {
// ...
}
在实现粘性事件时,我们需要在 XEventBus 中维护一个粘性事件的集合。当订阅者注册时,如果其订阅方法设置了 sticky 属性,那么将会收到集合中保存的对应类型的粘性事件。
这样,通过对 XEventBus 的优化与拓展,我们可以实现一个功能更加丰富、性能更优的事件总线。
XBus的开源地址。关于XBus的具体使用可以参考。简述如下。
根build.gradle中添加仓库来源地址
allprojects {
repositories {
...
maven {
url 'https://lucasxu01.github.io/maven-repository/'
}
}
}
app项目级别build.gradle中添加依赖
implementation 'com.lucas:xbus:1.0.0'
implementation 'com.lucas:xbus-annotations:1.0.0'
annotationProcessor 'com.lucas:xbus-apt-processor:1.0.0'
Antivity的onCreate方法中注册bus:
XEventBus.getDefault().register(MainActivity.this);
定义一个自己的Event事件:
public class WorkEvent {
private int num;
public WorkEvent(int num) {
this.num = num;
}
public int getNum() {
return num;
}
}
对应的Activity中注册方法
@Subscribe(priority = 1)
public void onEvent(final WorkEvent event) {
Log.e(TAG, "onEvent: " + " Thread, WorkEvent num=" + event.getNum());
}
发送事件进行调用
XEventBus.getDefault().post(new WorkEvent(5))
若想使用apt方式代替注解,可在bus注册时这样注册:
AptMethodFinder aptMethodFinder = new AptMethodFinder();
XEventBus.builder().setMethodHandle(aptMethodFinder).build().register(this);
简单介绍一下其他技术方案实现的技术总线。
RxBus是基于RxJava实现的,需要额外导入RxJava RxAndroid等库,因此库体积还是较大的。使用RxBus你得了解rxjava的原理,对于不使用rxjava的项目来说,成本太高了,而且容易内存泄露。想了解具体实现细节的可参考《使用RxJava实现的EventBus》。
使用起来大概是如下:
RxBus.getInstance().toObservable(MsgEvent.class).subscribe(new Observer<MsgEvent>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(MsgEvent msgEvent) {
//处理事件
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
RxBus.getInstance().post(new MsgEvent("Java"));
ASM(Abstract Syntax Machine)是一个Java字节码操作和分析框架。ASM用于动态生成、转换或者操作Java字节码。在Android中,ASM通常用于性能优化、代码注入、AOP(面向切面编程)等场景。然而,它并不是一个事件总线的典型实现方式。基于ASM实现的时间总线有:BusUtils;
如果你确实想使用ASM实现事件总线,可以尝试以下方法:
这种实现方式在实践中可能非常复杂,容易出现问题,而且可能导致性能和兼容性问题。因此,我们建议在实现事件总线时,优先考虑使用更为成熟、简便的方案,如EventBus、LiveData和ViewModel或RxJava,这些方法在通信和解耦方面表现出色,更为简便和高效,易于使用。
LiveData是Android Architecture Components库的一部分,它是一个可观察的数据持有类,能够在数据发生变化时通知订阅者。使用LiveData实现事件总线可以确保通信在主线程上执行,而且与应用程序的生命周期紧密结合,从而避免内存泄漏。
以下是实现基于LiveData的Bus的简要步骤:
基于LiveData的事件总线具有以下优点:
Flow是Kotlin协程库的一部分,它提供了一种声明式、响应式的编程模型,能够更方便地处理异步事件流。使用Flow实现事件总线可以确保通信在主线程上执行,同时提供了更丰富的操作符和组合方式,能够处理更复杂的场景。
以下是实现基于Flow的Bus的简要步骤:
基于Flow的事件总线具有以下优点:
本文从设计到实现,详细介绍了一个简单的 EventBus 系统 - XEventBus。我们首先讨论了 EventBus 的基本概念、应用场景和优势,并阐述了 XEventBus 的设计目标。接着,我们通过分析 XEventBus 的核心组件和实现细节,介绍了订阅者方法查找、注册、事件发布和订阅者方法调用等核心功能。最后,我们还探讨了 XEventBus 的优化与拓展,包括缓存策略、优先级控制、延时处理和粘性事件等。
EventBus 主要适用于组件之间的松耦合通信,特别是在 Android 应用开发中,它可以简化 Activity、Fragment、Service 之间的消息传递。然而,EventBus 也有一定的局限性,例如:
尽管 EventBus 具有一定的局限性,但在适当的场景下,它仍然是一个非常有用的工具。随着技术的发展,我们可以期待 EventBus 的功能将不断完善,例如:
总之,本文通过实现 XEventBus,希望能为读者提供一个 EventBus 的入门示例,以便更好地理解和应用 EventBus 这一有用的工具。
https://www.jianshu.com/p/a5e89082d1b9
https://blog.csdn.net/u011213403/article/details/121267330
https://juejin.cn/post/6844903896700157966#heading-15
https://www.jianshu.com/p/2a8f9ac32e13