一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
创建
EventBus是否可以创建多个实例
通过源码分析,我们看到有一下几个地方可以获取实例
EventBus类
// 1
public EventBus() {
this(DEFAULT_BUILDER);
}
// 2
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类
// 3
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists." + " It may be only set once before it's used the first time to ensure consistent behavior.");
}
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
// 4
public EventBus build() {
return new EventBus(this);
}
- EventBus()使用默认参数创建了新的实例
- getDefault()使用单例获取EventBus的实例,每次获取的都是一致的
- installDefaultEventBus()会初始化EventBus类中的defaultInstance,也就是getDefault()获取的实例,但是它只能在defaultInstance为null时才能调用,否则会抛出异常
- 以上几种方法创建的实例都是使用的默认参数创建的,除此之外,EventBus支持通过构造者模式创建实例,通过EventBus.builder()获取EventBusBuilder对象,然后执行参数,最后调用build()方法创建EventBus的实例
所以EventBus支持创建多个实例。
注册
多次注册同一对象会能否注册成功
查看注册流程,来到subscribe()方法内部
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//是否重复注册
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//省略其他代码
}
可以看到先通过订阅事件类型从subscriptionsByEventType中取出订阅信息集合,如果不为空则判断subscriptions.contains(newSubscription),Subscription是如何判断是否包含的呢,先看看他的equals()方法
public boolean equals(Object other) {
if (other instanceof Subscription) {
Subscription otherSubscription = (Subscription) other;
return subscriber == otherSubscription.subscriber
&& subscriberMethod.equals(otherSubscription.subscriberMethod);
} else {
return false;
}
}
判断中使用了subscriberMethod类的equals()方法
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof SubscriberMethod) {
//这里只展示了主流程,所以修改了部分源码
SubscriberMethod otherSubscriberMethod = (SubscriberMethod)other;
StringBuilder builder = new StringBuilder(64);
builder.append(method.getDeclaringClass().getName());
builder.append('#').append(method.getName());
builder.append('(').append(eventType.getName());
methodString = builder.toString();
return methodString.equals(otherSubscriberMethod.methodString);
} else {
return false;
}
}
由于篇幅问题,适当修改了下SubscriberMethod的equals()方法,阅读起来更清晰,可以看到,判断的核心在于判断拼接的字符串className#methodName(eventTypeName
是否相等。
当我们重复注册的时候,这个结果一定是相等的,所以就会抛出EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "eventType)
异常。
所以重复注册会抛出异常。可以调用isRegistered()判断是否注册过。
注册的时候哪些方法会被注册
我们以反射查找订阅方法为例,查看具体实现
private void findUsingReflectionInSingleClass(FindState findState) {
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
if (findState.checkAdd(method, eventType)) {
//省略其他代码
}
}
}
}
}
}
当获取到订阅类的所有方法后,首先对修饰符进行筛选
- (modifiers & Modifier.PUBLIC) != 0 方法必须是公开的
- (modifiers & MODIFIERS_IGNORE) == 0 (MODIFIERS_IGNORE=Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC) 方法必须是非静态的,非抽象的,以及不是编译过程中添加的,BRIDGE和SYNTHETIC不是Java的关键字,没有在公开的api公开。 然后对参数进行筛选(parameterTypes.length == 1)
- 所以方法必须只有一个参数 然后是注解(subscribeAnnotation != null)
- 方法必须有@Subscribe注解
- 最后是检查订阅方法是否已经添加过了,只有没添加过的才保留 所以只有满足以上5种条件的方法才会被注册
注册对象没有被 @Subscribe 注解的方法会如何
从上面得知,如果注册对象没被@Subscribe注解的方法,那么最后查找结果就是空数据,查看findUsingReflectionInSingleClass()方法的调用处
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//省略其他代码
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
}
}
当收集的订阅方法是空的时候,直接抛出EventBusException("Subscriber " + subscriberClass " and its super classes have no public methods with the @Subscribe annotation"
异常)
所以注册对象没有被 @Subscribe 注解的方法会抛出异常
父类被@Subscribe注解的方法会被注册吗
还是以反射查找为例
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
}
可以看到订阅方法的解析是有层级的,先查找当前类符合条件的,再通过moveToSuperclass()查找上一级,直到findState.clazz为null
所以父类中被 @Subscribe 注解的方法有可能被注册。至于最后结果,还需要判断该方法是否满足上面说到的其余4种条件
事件
post时,如果没有找到 eventType 对应的注册方法会如何
查看发送事件部分的代码,最后定位到postSingleEvent()方法
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
//省略部分
boolean subscriptionFound = postSingleEventForEventType();
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
当事没有找到订阅方法的时候subscriptionFound=true
,此时会根据logNoSubscriberMessages
决定是否打印日志,而且如果当前事件类型不是NoSubscriberEvent
和SubscriberExceptionEvent
,并且配置了sendNoSubscriberEvent=true
,则会发送一个NoSubscriberEvent
事件。
粘性事件原理
粘性事件的实现比较简单,先在发送粘性事件之前将其保存一份,然后在注册完订阅者的时候如果发现粘性事件,就发送出去
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event);
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//省略订阅等其他信息
if (subscriberMethod.sticky) {
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
存储粘性事件stickyEvents的结构是ConcurrentHashMap,所以同一个粘性事件只会缓存最近一个。
反注册
多次反注册同一对象,或者反注册一个未被注册过的对象会如何?
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
//反注册
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
反注册一个存在的订阅者会将它从typesBySubscriber中移除,再次反注册subscribedTypes就会为null,直接打印日志。