一、简介
EventBus是一个事件“发布/订阅”总线,可用于Android或者Java项目中。它具有以下优点:
- 简化组件间的通信
- 解耦事件发布方和订阅方;
- 很好的实现在Activities、Fragments和后台线程间通信;
- 避免复杂的易错的依赖和生命周期问题;
- 使代码更简洁;
- SDK体积很小(约50k);
- 已经在上亿的安装应用中使用;
- 具有高级特性,如跨线程事件分发、按优先级分发等。
下文我们把Publisher翻译成
事件发布器
,把Subscriber翻译成事件订阅者
,Event翻译成事件
,EventBus翻译成总线
。
二、EventBus使用三部曲
1. 定义事件类型
public class MessageEvent {
public String msg;
}
2. 事件订阅
public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
// 将事件订阅者注册到总线中
EventBus.getDefault().register(this);
}
@Override
protected void onPause() {
super.onPause();
// 将事件订阅者从总线中注销掉
EventBus.getDefault().unregister(this);
}
// 定义事件接收器处理事件的方法
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.d(TAG, "onMessageEvent="+event.msg);
}
}
3. 事件发布
public void sendEventMsg(View v){
// 事件发布器发布事件
MessageEvent event = new MessageEvent();
event.msg = "hello";
EventBus.getDefault().post(event);
}
三、源码分析
正如官方文档介绍,sdk真的很小。核心类EventBus.java就500行代码完成了所有功能。短小精悍、简洁易用、稳定高效,实为我辈开发之楷模。EvenBus核心任务就两个:1、事件订阅者的管理;2、事件的分发。先总的看下整体架构,类图大致如下:
类图1. EventBus的创建
EventBus SDK使用不需要任何初始化操作。在需要订阅或者发布事件时,通过EventBus.getDefault()
方法获取一个全局的EventBus的单例来执行相关操作。下面是SDK源码中单例创建的代码,没什么好说的标准的单例创建。
// double check lock
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
2. EventBus对事件订阅者
的管理逻辑
上面EventBus使用部分介绍了,事件接收器
的注册/注销是通过EventBus实例的register/unregister方法来完成的。我们重点分析下register方法的实现,unregister方法类似就不重复分析了。
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(Object subscriber)
方法接收一个Object对象作为入参。
- 通过
findSubscriberMethods(Class<?> subscriberClass)
方法分析出该对象的类型对象中所有事件处理方法。是通过反射的方式找到我们用@Subscribe
注解的方法,如下代码所示。 - 将Object对象和上面找到的每个事件处理方法都分别组成一个事件订阅者
Subscription
,也就是说如果一个订阅对象类里面实现了多个事件处理方法,那就会生成多个事件订阅者。然后将这些事件订阅者对象放到集合Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
中保存起来,待事件分发时使用。subscriptionsByEventType是一个map,map的key是事件类型,value是相同事件的订阅者的列表。插入到列表的时候是按照订阅者优先级顺序插入的,后续事件分发的时候就会按照事件优先级来顺序分发。
类比上面使用说明中的例子,我们传入的Object对象是一个Activity
对象,事件处理方法就是onMessageEvent
方法。在EventBus中是将这个Activity
对象和onMessageEvent
方法和成了一个事件订阅者Subscription
,然后存在数剧集合中的。上面类图中事件订阅者Subscription
的结构清楚的展示了这一点。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods = findState.clazz.getDeclaredMethods();
for (Method method : methods) {
int modifiers = method.getModifiers();
// 1、必须是public类型,且不能是被static、abstract修饰的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 2、方法有且只有一个参数
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
//3、方法必须是被Subscribe注解的方法
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 (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 (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");
}
}
}
unregister的过程我们就不分析了,类似register的逆过程,就是从数据集合中移除对应的事件订阅者的过程。
3. EventBus对事件的分发逻辑
事件发布post(Object event)
方法接收一个事件对象作为入参。还记得我们事件订阅者是存在以事件类型为key的map中,那事情就简单了,通过入参的事件类型获取所有订阅此事件的事件订阅者列表。
- 执行事件订阅者
Subscription
中事件订阅对象的事件处理方法;
void invokeSubscriber(Subscription subscription, Object event) {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
}
- 跨线程分发
EventBus中有两个poster。mainThreadPoster
:如类图所示它继承了Handler,关联的是主线程的Looper,所以可以通过mainThreadPoster将事件发送到主线程执行;backgroundPoster
:如果所示本身是个Runnable,关联了一个线程池,所以它是将事件发送到线程池去执行的。
还有一种类型是不适用上面的poster,直接就在事件发布器调用post(Object event)
方法的线程执行事件处理方法,这就实现了在当前线程分发事件。
四、学习
1、Double-Check单例创建法
- Double-Check是为了执行提高效率,只在首次获取实例的时候有线程同步的开销,后续获取实例时不涉及同步;
- 不同步的引用对象不是线程安全的,除double和float外的基本变量类型是线程安全的。所以在double check模式下引用对象、double和float变量都需要使用volatile 关键字来保证其线程安全。
2、注解
RetentionPolicy.RUNTIME类型的注解可以在运行时通过反射的方式获取到注解的内容。如下运行时注解Subscribe
可应用到方法上。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
运行时通过反射获取到注解的相关信息。
Method[] methods = findState.clazz.getDeclaredMethods();
for (Method method : methods) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
}
}
3、多线程编程
根据前文的介绍可以了解整个框架中的临界资源应该是这个Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
数据集合对象了。在注册/注销时间订阅者和事件分发是都需要操作这个数据集合,可能出现多个线程同时注册/注销时间订阅者,或者注册/注销时间订阅者和事件分发不在同一个线程,这些场景都有多线程的问题。
我们来看看作者是怎么处理的。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
public synchronized void unregister(Object subscriber) {
... ...
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
... ...
return false;
}
对subscriptionsByEventType
的操作使用synchronized
关键字来同步。另外对于事件订阅者列表使用CopyOnWriteArrayList
来同步修改列表,同时让列表的遍历更加高效。
网友评论