EventBus 深入学习五之注册
订阅者的注册 + 消息推送
1. 注册
先贴出注册代码, 可以可到和 Guava 相比没什么大的区别, 主要的点在内部实现上,一个是如何获取注册信息;一个是如何保存注册关系
/**
* Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
* are no longer interested in receiving events.
* <p/>
* Subscribers have event handling methods that must be annotated by {@link Subscribe}.
* The {@link Subscribe} annotation also allows configuration like {@link
* ThreadMode} and priority.
*/
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 查询注册方法的核心
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 维护订阅关系的核心代码
subscribe(subscriber, subscriberMethod);
}
}
}
获取订阅信息 (构建 SubscriberMethod
)
注解方式获取
从下面的代码可以看出,获取注解方法的流程是:
- 获取类的所有方法
- 只有一个参数的方法,判断是否有
@subscribe
注解 - 其他都过滤(视情况是否抛异常出来)
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
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) {
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");
}
}
}
对比Guava的获取注解方法, 实现的主要区别是Guava多了一个获取超类的过程
- Guava获取所有的超类, 根据每个类的 `getDeclaredMethods` 获取所有的方法,然后判断是否有注解
- Greenrobot 则是直接调用类的 `getDeclaredMethods` 获取所有方法, 然后判断是否有注解
上面两个有什么区别 ?
class.getDeclaredMethods
可以获取类所有申明的方法,也就是说 private, protected, 默认,public四个作用域的都可以获取到,换句话说,Guava的订阅者方法可以是私有的!!!,即便父类的私有方法也是可以的, static也无所谓
Greenrobot
中限制了方法的作用域共有的非静态方法,有且只有一个参数,而且只是对当前类而言
非注解方式
支持非注解方式进行注册,主要借助SubscriberInfoIndex
来指定注册方法 。我们可以倒推一下这个设计思路:
- 注册,首先是要确定将类的哪些方法注册到
EventBus
- 排除掉注解方式;还有一种常见的就是我们定义一种方式,可以将我们需要注册的方法直接返回
- 定义一个接口,用于返回注册类的所有订阅信息,然后把这个接口也丢给
EventBus
下面贴一个非注解方式的测试用例,方便理解, SubscriberInfoIndex
就是我们定义用于返回所有的订阅关系的接口,通过调用 EventBus.addIndex
(非线程安全)方法告知eventbus
对象
public class EventBusIndexTest {
private String value;
@Test
/** Ensures the index is actually used and no reflection fall-back kicks in. */
public void testManualIndexWithoutAnnotation() {
SubscriberInfoIndex index = new SubscriberInfoIndex() {
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
Assert.assertEquals(EventBusIndexTest.class, subscriberClass);
SubscriberMethodInfo[] methodInfos = {
new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class)
};
return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos);
}
};
EventBus eventBus = EventBus.builder().addIndex(index).build();
eventBus.register(this);
eventBus.post("Yepp");
eventBus.unregister(this);
Assert.assertEquals("Yepp", value);
}
public void someMethodWithoutAnnotation(String value) {
this.value = value;
}
}
下面则开始进入正式的代码纬度分析
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState); // 这里是获取订阅方法相关信息的核心
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState); // 这里做了兼容, 以注解方式去扫描获取订阅方法
}
// 再去扫父类的订阅信息
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
核心代码如下, 有意思的一点就是面对继承关系的处理,到底是选择子类的订阅关系还是父类的订阅关系
private SubscriberInfo getSubscriberInfo(FindState findState) {
// 下面的判断逻辑主要针对子类继承了父类中的订阅方法时, 返回子类的订阅信息
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
维护注册关系
通过上面可以将订阅者所有注册方法找出来,找出来之后当然是要存起来,也就是这一小节的内容,如何存,存了之后如何用
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
这个东西保存的就是Event
->订阅方法
的映射关系, 相当于Guava的SubscriberRegistry
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//
// 开始将订阅信息塞入 subscriptionsByEventType 时间-注册关系映射表中
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);
}
}
// 根据排序,从后往前判断,应该插入什么位置,也就是说相同的优先级,越早注册的,越先获取消息
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// typesBySubscriber 保存订阅对象,和订阅对象监听的所有事件类的映射关系, 下面的逻辑就是保证这个映射关系的完整性
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 这里就是对粘性事件的处理
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
##2. 取消注册
这个没什么好说的,就是上面的逆过程
3. 小结
上面分析了注册的流程,基本上思路和 Guava 的没什么大的区别,不同的是,做了更多的东西,实现了更多的功能,其中我们可以参考的几个设计思路
- 经典单例模式的
defaultInstance
, 这个在jdk里面用得非常多,比如Boolean.True
,Collections.EMPTY_LIST
,会给一些默认的实例,不用每次都去创建,上面的实现其实有不同的,实际对比之后比较明显可以感知 - 注解方式注册
- 如何获取某注解的方法,这个使用和guava的有些区别,特别是对超类以及作用域的情况处理
- 非注解方式注册
- 典型的借用辅助类(
SubscriberInfoIndex
)来完成预期目标
- 典型的借用辅助类(
此外实现的细节上也可以看看,参考优秀的写法才能提高自己的书写质量,当然每个人的习惯都不一样,就比如对EventBus类中的某些用法,本人持保留意见
大量使用内部类,且属性基本上都直接访问,不通过Getter/Setter方法 (讲道理,对这种方式还是不太习惯)
EventBus中大量的配置属性,个人倾向使用Option配置类来做,会使类结构更加清晰