前述
虽然该库已经停止维护了,但是因为它注册监听的便捷,所以项目中还是用到了该库。想当初刚使用该库时候的方便,真真是乐不思蜀啊!功能就不赘述了,都停止维护还在研究这个就有点尬尴了。
问题描述
post
一个消息,Subscriber
注解的方法总是执行两次。问题描述起来就很简单了,那是因为我并没有描述在网上寻找原因跟解决方案的艰辛,并且并未找到同样的问题,也隐藏了导致这个原因的重要细节描述,伪代码贴出来看大家是否知道是什么问题:
//监听:
@Subscriber(tag = "tag")
public void onXXX(Map obj) {
//...
}
//发送消息
HashMap params = new HashMap<>();
params.put("aaa", "aaa");
params.put("bbb", "bbb");
EventBus.getDefault().post(params, "tag");
复制代码
解决方案
解决方法就是吧@Subscriber
注解的方法入参由Map
改成HashMap
就好了。这个问题按说把发送跟接收的数据类型统一就可以避免,但为什么会执行两次,如果压根就接收不到那也会比较早的发现问题了。为了能睡个安稳觉,也要把根本原因找到。(为了不耽误不想看我废话的朋友的时间,先把解决方法告诉大家,下面来看看原因)
源码追踪
既然已经说了是废话了,那还是啰嗦一点。监听跟分发肯定是分两步走的,我们也分两个步骤走:
步骤一:注册
当通过 EventBus.register(Object subscriber)
注册对象后,SubsciberMethodHunter
类做如下操作:
- 通过遍历
subscriber
里所有通过@Subscriber(tag="tag",mode=ThreadMode mode)
注解的方法Method method
,- 获取到
tag
及method
的入参类型,封装成EventType eventType
对象(以供后期消息分发) - 获取到
mode
,把method
、eventType
、mode
封装成TargetMethod subscribeMethod
,再将subscriber
跟subscribeMethod
封装成Subscription newSubscription
(以供消息分发后调用method
来执行方法) - 以
eventType
为key
,将newSubscription
放进List
以List
为value
,用map
维护期来(放进List
,是因为一种类型(tag
)的消息你可能在多个对象里注册哦)
- 获取到
源码片段(代码有删减变动,仅为方便学习)
public class SubsciberMethodHunter {
Map> mSubcriberMap;//
/**
* 查找订阅对象中的所有订阅函数,订阅函数的参数只能有一个.找到订阅函数之后构建Subscription存储到Map中
*
* @param subscriber 订阅对象
*/
public void findSubcribeMethods(Object subscriber) {
Class> clazz = subscriber.getClass();
// 查找类中符合要求的注册方法,直到Object类
while (clazz != null && !isSystemCalss(clazz.getName())) {
// 获取到注册对象的所有方法并遍历
final Method[] allMethods = clazz.getDeclaredMethods();
for (int i = 0; i < allMethods.length; i++) {
// 获取到subscriber中通过@Subscriber()注解的所有方法,及Subscriber对象(tag,mode)
Subscriber annotation = method.getAnnotation(Subscriber.class);
if (annotation != null) {
// 获取参数类型,封装EventType
Class>[] paramType = method.getParameterTypes();
EventType eventType = new EventType(paramType, annotation.tag());
// 封装TargetMethod
TargetMethod subscribeMethod = new TargetMethod(method, eventType, annotation.mode());
// 封装Subscription
Subscription newSubscription = new Subscription(subscriber, subscribeMethod);
// 将Subscription放进List
CopyOnWriteArrayList subscriptionLists = mSubcriberMap.get(event);
subscriptionLists.add(newSubscription);
// 将事件类型key和订阅者信息存储到map中
mSubcriberMap.put(event, subscriptionLists);
}
}
}
// 获取父类,以继续查找父类中符合要求的方法
clazz = clazz.getSuperclass();
}
}
public final class EventType {
/**
* 参数类型
*/
Class> paramClass;
/**
* 函数的tag
*/
public String tag = DEFAULT_TAG;
}
复制代码
步骤二:发送消息
当通过 EventBus.post(Object param, String tag)
发送消息后,EventBus
类做如下操作:
- 将
event
跟tag
封装成EventType
放进队列(这里就很快的能想到注册时为什么以EventType
为key
了) - 通过
EventDispatcher
类执行队列,进行消息的分发:- (重点)获取传参
param
的Class
类、父类、接口类分别跟tag
封装的EventType
的集合List
(此举好处:eventTypes post()
发送HashMap
参数,以HashMap
、AbstractMap
、Map
都可以收到消息) - 通过
List
从注册时维护的eventTypes Map
里获取到> mSubcriberMap Subscription
集合 - 遍历
CopyOnWriteArrayList
,从Subscription
中获取到订阅对象、订阅方法对象、mode(接收线程),进行消息分发
- (重点)获取传参
源码片段(代码有删减变动,仅为方便学习)
public final class EventBus {
//放进队列,执行分发
public void post(Object event, String tag) {
mLocalEvents.get().offer(new EventType(event.getClass(), tag));
(EventDispatcher)mDispatcher.dispatchEvents(event);
}
private class EventDispatcher {
MatchPolicy mMatchPolicy = new DefaultMatchPolicy();
//执行队列
void dispatchEvents(Object aEvent) {
Queue eventsQueue = mLocalEvents.get();
while (eventsQueue.size() > 0) {
deliveryEvent(eventsQueue.poll(), aEvent);
}
}
/**
* 根据aEvent查找到所有匹配的集合,然后处理事件
*/
private void deliveryEvent(EventType type, Object aEvent) {
// 重点来了,获取传参`param`的`Class`类、父类、接口类分别跟`tag`封装的`EventType`的集合`List eventTypes`
List eventTypes = mMatchPolicy.findMatchEventTypes(type, aEvent);
// 迭代所有匹配的事件并且分发给订阅者
for (EventType eventType : eventTypes) {
//处理事件
handleEvent(eventType, aEvent);
}
}
//从注册时维护的mSubcriberMap里获取到Subscription集合,遍历执行消息
private void handleEvent(EventType eventType, Object aEvent) {
List subscriptions = mSubcriberMap.get(eventType);
if (subscriptions == null) {
return;
}
for (Subscription subscription : subscriptions) {
// 处理事件
eventHandler.handleEvent(subscription, aEvent);
}
}
}
}
复制代码
重点:获取传参param
的Class
类、父类、接口类分别跟tag
封装的EventType
的集合List
public class DefaultMatchPolicy implements MatchPolicy {
@Override
public List findMatchEventTypes(EventType type, Object aEvent) {
Class> eventClass = aEvent.getClass();
List result = new LinkedList();
while (eventClass != null) {
//将class类封装成EventType添加进集合
result.add(new EventType(eventClass, type.tag));
//获取实现的接口类封装成EventType添加进集合
addInterfaces(result, eventClass, type.tag);
//获取父类,while()继续执行
eventClass = eventClass.getSuperclass();
}
return result;
}
//获取实现的接口类封装成EventType添加进集合
private void addInterfaces(List eventTypes, Class> eventClass, String tag) {
Class>[] interfacesClasses = eventClass.getInterfaces();
for (Class> interfaceClass : interfacesClasses) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(new EventType(interfaceClass, tag));
addInterfaces(eventTypes, interfaceClass, tag);
}
}
}
}
复制代码
注意: 按说这没什么毛病啊,你监听我消息类型的父类或是接口类都能收到消息,是的,你可能忘了我遇到的问题是啥了,收到两次消息啊!断点里我看到findMatchEventTypes()
返回的List
里有两个Map
类的EventType
,而我@Subscriber
监听的正是Map
类型的参数。看下问题出在哪:
// HashMap 继承 AbstractMap,实现 Map
public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {
}
// AbstractMap 实现 Map
public abstract class AbstractMap implements Map {
}
复制代码
可能是我对java的继承跟实现有理解的不够深入,这样做的好处在哪里?大神可以留言指点指点。
彻底的解决方案
EventBus.setMatchPolicy(MatchPolicy policy);
设置订阅函数匹配策略为StrictMatchPolicy
,这样的话就是精确匹配了,发什么数据类型,必须以什么类型接收。DefaultMatchPolicy.findMatchEventTypes()
里的List
去重
最后
第一次写技术文章,水平比较菜,主要以学习为主,说的不对的地方还望指点,不喜勿喷哦!喷我我会尴尬的哦,尬尴的哦,尴尬,尬尴尴尬。。。