0%

系统源码理解-EventBus原理

注: 本文的EventBus版本为 3.0

Subscrib 注解

自 3.0 以来,EventBus 使用 @Subscrib 注解来标记订阅事件的方法,方法命名随意。并不用像以前那样指定方法的命名:

1
2
@Subscribe
public void testEventBus(Object obj){ }

这个注解的定义很有个性,可以看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Documented
@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Target({ElementType.METHOD}) // 作用在方法上
public @interface Subscribe {

// 指定事件订阅方法所在的线程模式,也就是决定订阅方法是在哪个线程,默认是POSTING模式
ThreadMode threadMode() default ThreadMode.POSTING;

// 是否支持粘性事件
boolean sticky() default false;

// 优先级,如果指定了优先级,则若干方法接收同一事件时,优先级高的方法会先接收到。
int priority() default 0;
}

其中有几种线程模式,有以下几种:

  • ThreadMode.MAIN:如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。

  • ThreadMode.ASYNC:与ThreadMode.MAIN_ORDERED相反,无论在哪个线程发送事件,都将事件加入到队列中,然后通过线程池执行事件

  • ThreadMode.POSTING:默认的线程模式,在哪个线程发送事件就在对应线程处理事件,避免了线程切换,效率高。

  • ThreadMode.MAIN_ORDERED:无论在哪个线程发送事件,都将事件加入到队列中,然后通过Handler切换到主线程,依次处理事件。

  • ThreadMode.BACKGROUND:与ThreadMode.MAIN相反,如果在子线程发送事件,则直接在子线程处理事件;如果在主线程上发送事件,则先将事件入队列,然后通过线程池处理事件。

注册

注册过程很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
EventBus.getDefault().register(this);

//注册源码
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass(); // 获取传入的要注册类的字节码文件
List<SubscriberMethod> subscriberMethods =
subscriberMethodFinder.findSubscriberMethods(subscriberClass);

synchronized (this) {

// 遍历订阅方法封装类的集合
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}

根据源码可以直到,这个方法就做了两件事情:

  • 根据传入的参数object ,获取其 Class ,然后通过这个 Class 获取所有的方法(当然,首先看有没有缓存),查看经过 @Subscrib 修饰的方法,即这个类中所有的订阅方法(会做封装),生成一个list
  • 遍历上述生成的 list ,给 2 个 Map 填充数据:subscriptionsByEventType以 event (订阅方法的参数)的类型(Class)为key,value 为订阅方法list(CopyOnWriteArrayList);typesBySubscriber 以register时传入的对象为key ,value 为 这个对象所有订阅方法所订阅的事件。
1
2
3
4
5
6
//EventBus中变量的声明

//可以根据event(订阅方法中的参数)类型 获取所有订阅方法
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//根据注册对象,获取这个对象上所有的订阅方法
private final Map<Object, List<Class<?>>> typesBySubscriber;

反注册

反注册也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
EventBus.getDefault().unregister(this);

public synchronized void unregister(Object subscriber) {

List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);

// 如果集合不为null
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());
}
}

刚才在注册时候,说了其中一个 map 为 typesBySubscriber,它以注册对象为 key ,value 为这个对象中所有注册方法所注册的event 类型的列表。所以在反注册的时候,

  1. 首先通过传入的对象,获取 注册的 event 的列表
  2. 遍历这个列表,获取 event 的类型,然后通过这个类型在 subscriptionsByEventType 查找(经过封装的)订阅方法,根据封装在里面的 注册对象 是否是当前 unregister 传入的对象来判断,如果是当前传入的对象,就移除这个经过封装的订阅方法

post 发布事件

post的使用也很简单:

1
EventBus.getDefault().post(new Object());

注册的时候,说了有一个map 为 subscriptionsByEventType ,以 event 的类型为key ,存储了所有订阅了这中 event 的(经过封装的)方法。post 的时候,根据post发送的事件类型(post方法的参数的 Class )从 subscriptionsByEventType 这个集合中获取到所有的订阅方法。之后依次通过反射调用这些方法:

1
2
3
4
5
6
7
8
9
10
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);
}
}

其中, subscription.subscriberMethod.method 是 Method 类型的,用过反射的话,就知道它可以直接 invoke ,它的定义是这样的:

1
public native Object invoke(Object obj, Object... args)

第一个参数 obj 指的是用 obj 这个实例来调用这个方法(因为一个类可能会有多个实例,非静态方法需要指定一个实例来调用这个方法),后面的 args 就是方法需要传入的参数。

所以,在 invoke 的时候,subscription.subscriber 我们应该很容易知道是 register 时传入的那个 Object !由此,我们一个消息就形成了闭环。

最后问题,支持跨进程吗?

不支持跨进程,因为单例。经过上述分析,我们知道 EventBus 的注册、反注册、post 都是通过:EventBus.getDefault() 实现,我们看下它的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class EventBus {

static volatile EventBus defaultInstance;

/** Convenience singleton for apps using a process-wide EventBus instance. */
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;
}

所以我们都是基于一个 static 类型的 defaultInstance去做一系列操作,由于跨进程后,静态会失效,所以,EventBus 并不能跨进程

以上参考自掘金刘洋巴金的博客,如果链接变得不可访问,可以参看这篇文章的Copy

谢谢你的鼓励