文章目录
事件总线模式基于发布-订阅机制
实现,它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,从而达到一种解耦的目的。事件总线模式主要由三个部分构成,即事件
、事件总线
和事件倾听器
,其中,事件
为事件源产生,在事件总线上传播,传播的方式是采用广播的方式;事件总线
是实现事件传播的"基础设施",可理解为事件总线模式的“调度中心”,所有在系统中阐述的事件都将在事件总线上进行广播,左右挂在在事件总线上的事件侦听器都将接收到在事件总线上发布的事件;事件侦听器
是用于侦听各种事件的对象,当事件侦听器接收到事件总线上发布的事件后,先判断事件的类型,如果是自身感兴趣的事件,事件侦听器将进行相应的处理,修改自身的状态。事件总线模式的逻辑图如下:
1. EventBus框架
1.1 EventBus简介
EventBus是GreenDao开源的一个用于Android的事件发布-订阅总线框架,它旨在简化Android应用中Activity、Fragment、Threads、Services等各个组件之间进行通信的复杂度,避免使用广播、接口等通信方式所带来的的诸多不便,同时从最大成程度上降低组件之间的耦合度。在EventBus框架中,主要包含Event(事件)
、Subsciber(事件订阅者)
和Publisher(事件发布者)
三个角色,其中Event表示的是任意类型的事件,EventBus会根据事件类型进行全局广播通知;Subscriber
表示事件订阅者,能够接收EventBus广播出来的事件,并对其感兴趣的事件进行处理。事件处理的方法名可以任意指定,但需要在方法添加@subcribe
注解和指定线程模式;Publisher
为事件发布者,可以再任意线程里发布事件。EventBus框架工作原理:
1.2 EventBus基本使用
1.2.1 添加Gradle依赖
implementation 'org.greenrobot:eventbus:3.1.1'
1.2.2 定义事件
EventBus的事件可以为任意类型,比如String、int、类等等,这里我们定义一个类EncodeEvent为例,该类用于描述编码器相关编码信息,它包含帧率、码率两个成员属性。该类表示一个事件,编码器每编码一帧视频,就生成这么一个事件。EncodeEvent类定义如下:
public static class EncodeEvent {
private String fps;
private String bitrate;
public StreamEvent() {
}
public StreamEvent(String fps, String bitrate) {
this.fps = fps;
this.bitrate = bitrate;
}
public void setFps(String fps) {
this.fps = fps;
}
public String getFps() {
return fps;
}
public void setBitrate(String bitrate) {
this.bitrate = bitrate;
}
public String getBitrate() {
return bitrate;
}
}
1.2.3 准备订阅者
-
首先,注册/注销订阅者。
通过调用EventBus的register/unregister方法实现,假如我们需要在Activity、Fragment、Service等组件中注册订阅者,那么订阅者的生命周期尽量与这些组件生命周期保持同步。如果订阅者注册在子线程等其他地方,就需要看情况注销订阅者,以免出现内存泄漏。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
-
其次,定义一个订阅者方法(
事件处理方法
)并为其添加Subscribe注解。订阅者方法将用于接收自己感兴趣的事件,所谓感兴趣的事件,就是方法中传入的参数类型。需要注意的是,在添加Subscribe注解时,需要传入名为threadMode参数,该参数表示的线程模式,默认值为
POSTING
,线程模式主要用于描述事件处理方法所在线程的特点。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEncodeEvent(EncodeEvent event) {
// do something
}
注解Subscribe支持三个参数,即threadMode、sticky和priority,其中threadMode用于指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING;sticky用于指定是否支持黏性事件,所谓黏性事件就是事件发布后,再注册订阅者方法仍然能够接收到事件,默认为false;priority用于指定事件订阅方法优先级,如果多个事件订阅方法可以接收相同事件的,则优先级高的先接收到事件,默认为0。注解Subscribe源码如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD}) public @interface Subscribe { // 指定事件订阅方法的线程模式,即在那个线程执行事件订阅方法处理事件,默认为POSTING // EventBus五种线程模式为: // 1. ThreadMode.POSTING:表示事件处理方法所在的线程与发布事件的线程在同一个线程; // 2. ThreadMode.MAIN:表示事件处理方法所在的线程为主线程(UI线程),因此不能进行耗时操作; // 3. ThreadMode.BACKGOUND:表示事件处理方法的线程不是主线程(或称后台线程),因此不能进行 // UI操作。如果发布事件的线程是主线程,那么事件处理方法将会开启一个后台线程;如果发布事件的 // 线程是在后台,那么事件处理函数就使用该线程; // 4. ThreadMode.ASYNC:表示事件处理方法始终会创建一个新的子进程运行,无论事件发布的线程是 // 哪一个。因此在该模式下,事件处理方法中不能进行UI操作。 // 5. ThreadMode.MAIN_ORDERED ThreadMode threadMode() default ThreadMode.POSTING; // 是否支持粘性事件,默认为false boolean sticky() default false; // 指定事件订阅方法的优先级,默认为0 int priority() default 0; }
1.2.4 发布事件
EventBus支持在任意线程、任意位置中发布事件,通过调用EventBus的post方法即可。
EventBus.getDefault().post(new EncodeEvent("25fps", "5Mbps"));
2. EventBus原理解析
从1.2小节中可知,当我们要使用EventBus时,首先需要调用EventBus.getDefault()方法来获取EventBus实例(或称事件总线
)。从EventBus.getDefault()方法源码可知,它采用了双重检查(DCL)
形式的单例模式来创建EventBus实例。在调用EventBus的构造方法中,又调用了另外一个重载构造方法,并且传入了一个值为DEFAULT_BUILDER
的参数,通过查看源码可知,这个DEFAULT_BUILDER就是EventBusBuilder的实例,该实例主要用于对EventBus进行配置,这里采用了建造者模式。EventBus.getDefault()方法等源码如下:
// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
// 单例模式
// 实例化EventBus
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
// EventBusBuilder用于配置EventBus
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
// 构造方法
// 多条EventBus,订阅者可以注册到不同的EventBus
public EventBus() {
this(DEFAULT_BUILDER);
}
// 构造方法
EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
// Map<Class<?>, CopyOnWriteArrayList<Subscription>>
// 事件类型(key)-订阅者列表(value)集合
subscriptionsByEventType = new HashMap<>();
// Map<Object, List<Class<?>>>
// 订阅者(key)-事件列表(value)集合
typesBySubscriber = new HashMap<>();
// Map<Class<?>, Object>
// 黏性事件集合
stickyEvents = new ConcurrentHashMap<>();
mainThreadSupport = builder.getMainThreadSupport();
// 用于在主线程发布事件
mainThreadPoster = mainThreadSupport != null ?
mainThreadSupport.createPoster(this) : null;
// 用于在后台线程发布事件
backgroundPoster = new BackgroundPoster(this);
// 用于在后台线程发布事件
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ?
builder.subscriberInfoIndexes.size() : 0;
// 注解方法寻找器
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
// 调用事件处理方法出现异常
// 是否需要打印日志
logSubscriberExceptions = builder.logSubscriberExceptions;
// 当没有订阅者订阅该事件
// 是否需要打印日志
logNoSubscriberMessages = builder.logNoSubscriberMessages;
// 调用事件处理方法出现异常
// 是否需要发送SubscriberExceptionEvent事件
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
// 当没有事件处理方法时
// 是否需要发送NoSubscriberEvent事件
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
// 调用事件处理方法出现异常
// 是否需要抛出SubscriberException异常
throwSubscriberException = builder.throwSubscriberException;
// 有继承关系的订阅者是否都需要发送
eventInheritance = builder.eventInheritance;
// 线程池
executorService = builder.executorService;
}
需要注意的是,与常见的单例模式不同的是,EventBus的构造方法作用域为public,这就意味着我们的应用可以创建不同的事件总线,然后订阅者可以注册到不同的事件总线上。由于不同的事件总线是隔离开的,因此订阅者只会收到它注册的事件总线发送的数据(事件)。接下来,我们分析该框架中订阅者注册、注销和事件发布过程。
2.1 订阅者注册过程
假如我们需要监听事件总线上感兴趣的事件,就需要在事件总线上进行注册,这样当事件总线发送数据后才会被通知到,这个注册过程被称为“订阅者注册”,通过调用EventBus的register()方法实现。该方法源码如下:
// \EventBus-master\EventBus\src\org\greenrobot\eventbus\EventBus.java
public void register(Object subscriber) {
// 1. 获取订阅者Class
Class<?> subscriberClass = subscriber.getClass();
// 2. 获取订阅者的所有订阅方法
// SubscriberMethod用于描述一个订阅方法
List<SubscriberMethod> subscriberMethods =
subscriberMethodFinder.findSubscriberMethods(subscriberClass);
// 3. 注册所有订阅方法
// 线程同步调用
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
在register()方法中,它首先会去获取订阅者类的Class对象(注释1
),然后调用SubscriberMethodFinder的findSubscriberMethods方法获取订阅者所有的事件处理方法(或称订阅方法,注释2
),最后遍历注册所有订阅方法,并调用EventBus的subscribe方法完成接下来的注册逻辑。(注释3
)
- 查找订阅者的所有订阅方法
对于订阅者所有订阅方法的查找,是通过调用SubscriberMethodFinder
的findSubscriberMethods方法实现的,该方法最终会返回一个订阅方法集合,而列表中元素的类型为SubscriberMethod
,该类用于描述一个订阅方法,用于保存订阅方法的Method对象
、线程模式(ThreadMode)
、感兴趣的事件类型(eventType)
、优先级(priority)
以及黏性事件标志(sticky)
等属性。findSubscriberMethods方法源码如下:
// \EventBus-master\EventBus\src\org\greenrobot\eventbus\SubscriberMethodFinder.java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 1. 从缓存中查找订阅者的订阅方法
// subscriberClass表示订阅者Class
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return<