前几篇文章分析了Activity的源码,后来看了看Window以及ViewRoot的相关源码,这些只是还没梳理,真的是视觉疲劳,来一个EventBus源码分析压压惊~。
其实关于EventBus的源码分析,网上也有很多,但是终究是别人的,以前也看过很多遍,但是决定是自己写下来比较好,座右铭:看一遍,不如自己写一遍。
关于EventBus的小总结我们先看下,带着知识点去看源码,然后分析为什么要这么写,还是必须这样写。
问题点6代码描述:
@Subscribe(threadMode = ThreadMode.MAIN , sticky = true)
public void onEventObject(EventModelA object) {
Log.e("-----------","我是子类");
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onEventObject(EventModelB event) {
Log.e("-----------","我是父类");
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onEventObject(EventInterfaceC event) {
Log.e("-----------","我是父接口");
}
EventBus.getDefault().postSticky(new EventModelA ());
/**
* Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
* are no longer interested in receiving events.
*
* Subscribers have event handling methods that must be annotated by {@link Subscribe}.
* The {@link Subscribe} annotation also allows configuration like {@link
* ThreadMode} and priority.
*/
/*
翻译:注册给定的订阅服务用于接收事件,订阅者必须调用unregister取消注册对象。
订阅服务器有事件处理方法,这些方法需要有ThreadModel(线程模式)和优先级的配置
*/
public void register(Object subscriber) {
//获取订阅者的Class对象
Class> subscriberClass = subscriber.getClass();
//通过这个类对象获取SubscriberMethod的集合,至于集合是什么后面讲到。
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//遍历SubscriberMethods集合,执行subscribe方法,参数为:订阅者的类和subscriberMethod。
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
register的注释翻译,这个不需要解释了, 方法内,先获取了订阅类的Class对象,把这个Class对象为参数,通过
findSubscriberMethods(Class) 方法获取到List集合泛型为SubscriberMethod, 从findSubscriberMethods方法名来看,就是:找到订阅的方法。 至于这个方法是怎么工作的我们下文分析。有必要看下SubscriberMethod类了:
/** Used internally by EventBus and generated subscriber indexes. */
public class SubscriberMethod {
final Method method; //Java通过反射获取到的订阅类的信息
final ThreadMode threadMode; //订阅类中订阅方法的线程模型TreadMode
final Class> eventType; //订阅方法参数的Class对象,也就是我们的Model的Class对象
final int priority; //订阅回调方法的优先级
final boolean sticky; //订阅回调方法是否是粘性的
/** Used for efficient comparison */
String methodString; //订阅方法的方法名
.........
通过代码可以看出 SubscriberMethod 其实就是一个Model类,它代表了:某一个订阅类的订阅方法信息。
那么回过头看下findSubscriberMethods(Class)就是:返回某一个注册订阅类的订阅方法的集合。然后接下来遍历这个集合,执行subscribe方法,参数为:订阅类(subscriber)和其某一个订阅的方法(subscriberMethod);
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//获得订阅方法的Class对象。
Class> eventType = subscriberMethod.eventType;
//创建Subscription,参数为:订阅者(subscriber)和订阅方法的信息类(subscriberMethod)
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//subscriptionsByEventType获得CopyOnWriteArrayList集合,泛型为Subscription
//eventType就是注册回调方法类的参数的Class对象,也就是上述例子中的:EventModelA的Class对象
//subscriptions也就是所有包含该回调方法的订阅类和该回调方法的信息集合
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//以该参数Class为Key值的Value是null,也就是同一进程中这是这个订阅类是第一个使用该Model为参数的回调方法
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
//这个异常是不是很常见?是不是发生在我们重复注册的时候?!
//原因:该参数Class对象对应的集合重复添加了该对象,所以报错
//如果我们重复注册了,那么之前已经执行过了,subscriptions.add(i, newSubscription);
//此时subscriptions 已经包含了该对象,所以报错
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中。
subscriptions.add(i, newSubscription);
break;
}
}
//通过subscriber,获取typesBySubscriber的value值,类型是什么呢?
List> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//在此处确定类型为:方法参数Model的的Class。
subscribedEvents.add(eventType);
//是否是粘性的
if (subscriberMethod.sticky) {
//是否需要检查父类和接口
/*
eventInheritance默认为true
*/
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).
//
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, 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);
}
}
}
上述方法注释比较多,不再赘述,就是为什么不能重复注册? 原因该参数Class对应的集合已经包含了该Subscription(订阅类和订阅方法的对应信息),问题点9解决了。checkPostStickyEventToSubscription方法内部最终调用的还是postToSubscription方法:
//通过invoke分发事件
/**
Subscription :注册过的订阅类和订阅方法的信息类
event:订阅方法内的参数
isMainThread:发布事件是否在主线程
*/
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
//直接在发布线程中invoke,通过反射调用注册订阅的方法。
invokeSubscriber(subscription, event);
break;
case MAIN:
//发布事件是否是在主线程,如果是在主线程,那么直接invoke。
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
//如果不是在主线程,那么事件交给UI主线程的Looper调度消息。总之还是在主线程
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
//区别就是MAIN_ORDERED优先判断将事件交给UI主线程的Looper调度消息
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
//如果发布事件在主线程,那么另起一个线程接收事件
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
//如果发布事件不在主线程,那么接受事件就在该线程运行
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
//无论发布消息是否在主线程,总是开启一个子线程接收订阅事件
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
postToSubscription方法是发布事件的,可以看下在subscribe方法最后判断了注册订阅的方法是否粘性的,如果是and之前发布过粘性事件(stickyEvent可以看成一个发布粘性事件的集合),此时执行postToSubscription方法,用于发布事件。发布时间内有五种类型,这五种类型解释了问题点的1,2,3,4。问题点8是不是解决一半了,解决了,在注册的时候,我们判断是否之前发布过粘性事件,如果是,在注册订阅的时候直接运行postToSubscription方法。至于另一半没解决就是:stickyEvent这个东西什么时候添加要发布的粘性事件呢?
发布事件,分两种:post 和 postSticky(粘性的) 我们非粘性的的post.
/** Posts the given event to the event bus. */
public void post(Object event) {
//获得当前发布事件的线程信息
PostingThreadState postingState = currentPostingThreadState.get();
//通过postingState获取当前线程的发布事件eventQueue集合。
List
通过上述代码,可以了解到,首先获取该线程信息对象,然后通过这个线程对象获取当前线程下的发布事件集合,在循环这个集合,执行postSingleEvent方法。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
//获取发布事件的Class对象
Class> eventClass = event.getClass();
//subscriptionFound 表示是否找到了订阅方法,默认是false
boolean subscriptionFound = false;
if (eventInheritance) {
//此时又看到了eventInheritance判断,也就是是否循环遍历获取以父类以及实现接口为参数的的订阅方法。
//看下lookupAllEventTypes方法的注释就行了
//lookupAllEventTypes注释:查找所有类对象,包括超级类和接口。还应该为接口工作。
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
//循环遍历集合,给以event集成的父类或者接口为参数的订阅方法发送事件;
for (int h = 0; h < countTypes; h++) {
Class> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//直接执行以event为参数的订阅方法
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//如果没有找到订阅方法,打印错误日志
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));
}
}
}
上述方法主要做的任务就是,先判断eventInheritance是否为true, 也就是是否需要向以Event继承的父类或者接口为参数的订阅方法执行postSingleEventForEventType方法。那么postSingleEventForEventType方法是干什么的?
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class> eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
//subscriptionsByEventType是不是很熟悉呢?我们在注册的时候给它添加过数据,就是
//subscriptionsByEventType.put(eventType, subscriptions);也就是:以参数Model的Class为key,
//以《订阅类,以该key为参数的订阅方法》为value,此处获得value,
//要这个value干什么呢?肯定是遍历发送事件啊!
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//遍历给注册订阅方法发送事件。
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//这个方法是不是注册的时候,注册最后判断是否有黏性事件,如果有最终执行此方法发送事件。这是不是就串联起来了?!
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
上述代码主要就是通过发送事件的Event的class为key值,从subscriptionsByEventType中获取value值,这个value值是个集合,泛型是:《订阅类,以该key为参数的订阅方法》,然后遍历该集合,循环执行postToSubscription给订阅者的相应的回调方法发送事件。
接下来看下postSticky方法:
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
首先执行的是往stickyEvents集合中添加数据,那么问题8的另一半是不是也解决了呢?!此时发送粘性事件先将事件存储到stickyEvents中,这种情况是订阅事件还没有声明订阅。如果订阅事件已经订阅了,如果post(event)就起作用了。。。
至此post 和 postSticky就算分析完了,附上一张流程图
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
//typesBySubscriber是不是很熟悉呢? 它是我们在注册的时候添加的数据,用订阅者为key,
//以集合为value,集合的泛型是该订阅类下的所有的订阅方法Class,
List> subscribedTypes = typesBySubscriber.get(subscriber);
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());
}
}
上述代码很简单,也就是通过subscriber为key获取该订阅类下的所有订阅方法集合,然后遍历该集合,执行unsubscribeByEventType方法,之后删除typesBySubscriber中的subscriber相关信息。那么unsubscribeByEventType方法内做了什么操作呢?
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
//维护集合subscriptionsByEventType的正确性。
//先获得以要删除的订阅类中的订阅方法参数Class为参数的subscriptions集合。
List subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
//遍历该集合
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
//如果集合中的订阅者等于subscription方法的订阅者,那么就删除。
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
上述代码整体维护的就是subscriptionsByEventType集合的正确性,操作的维度是以要删除的订阅类中的订阅方法中的参数为中心的。
解绑流程就是维护了typesBySubscriber和subscriptionsByEventType两个集合的数据
关于EventBus的添加,该功能是在EventBus3.0之后添加的新功能,据说能大幅提升性能?为什么能提升性能呢?
添加索引传送门
添加索引的方法是:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
此处的MyEventBusIndex是我们定义的自定义名称,看下addIndex的方法:
/** Adds an index generated by EventBus' annotation preprocessor. */
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if (subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
将index对象添加到了subscriberInfoIndexes集合中,那么SubscriberInfoIndex是什么呢?
/**
* Interface for generated indexes.
*/
public interface SubscriberInfoIndex {
SubscriberInfo getSubscriberInfo(Class> subscriberClass);
}
可以看到是个接口,那么实现类在哪里呢?就是我们定义的MyEventBusIndex类:
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
//每个订阅类
putIndex(new SimpleSubscriberInfo(TestActivity.class, true, new SubscriberMethodInfo[] {
//每个订阅方法的相关信息
new SubscriberMethodInfo("onEventObject", EventObject.class, ThreadMode.MAIN, 0, true),
new SubscriberMethodInfo("onEventObject", SecondEvent.class, ThreadMode.MAIN, 0, true),
new SubscriberMethodInfo("onEventObject", TestInterfaceEvent.class, ThreadMode.MAIN, 0, true),
new SubscriberMethodInfo("onEventObject", ThirdEvent.class, ThreadMode.MAIN, 0, true),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
//实现接口的方法
@Override
public SubscriberInfo getSubscriberInfo(Class> subscriberClass) {
//返回订阅类的相关信息(全部订阅方法的相关信息)
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
这个类是编译自动生成,不是手动添加的。那么在编译的时候,获取了订阅类的相关信息,在注册的时候是怎么用的呢?在上文注册源码分析的时候提到了这句代码:
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
那么subscriberMethodFinder方法做了什么呢?
List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//判断是否添加索引
if (ignoreGeneratedIndex) {
//没有添加索引
subscriberMethods = findUsingReflection(subscriberClass);
} else { //默认没有添加索引
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
ignoreGeneratedIndexb标记代表是否添加索引,默认是false,也就是没有添加索引,那么findUsingInfo方法是干什么的呢?
private List findUsingInfo(Class> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//获取存储的相关索引信息
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) { //索引信息不为null
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else { //索引存储为null,该方法内部就是通过反射获取订阅类的相关订阅信息
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;
}
我们可以知道,其实消耗时间的重头部分就是在注册的时候,因为注册的时候(没添加索引),需要通过Java的反射获取注册类的订阅方法相关信息,这部分是非常耗时间的,它发生在运行过程。但是添加完了索引之后,会在编译的时候生成一个类,该类包含了注册类的相关信息,发生在编译过程。
通过上述代码分析,原理可以总结两点:
1:源码架构:实际上维护的就是两个Map集合,分别是:以订阅方法的参数class为key, 然后存储相关订阅信息,value也就是整个应用以该Class类为参数的订阅类信息;另一个是以订阅类为key, 此时value为该订阅类的所有方法。
2:提高性能:第一点是关闭父类以及接口查找分发事件;第二点 添加索引,索引添加的原理就是提前在编译的时候加载好注册类的相关信息。
最后说下关于EvenBus提高性能的相关设置,可以在Application中设置如下:
EventBus.builder().addIndex(new MyEventBusIndex()).eventInheritance(false).installDefaultEventBus();
参考文献:【Bugly干货分享】老司机教你 “飙” EventBus 3