上一篇文章我们学习了EventBus的基本使用,本着知其所以然的道理,这篇文章我们来学习EventBus的源码。本次对于源码的理解,我会从初始化,注册,事件的发送,调用和解绑四个方面来解析。接下来进入今天的源码解析之路。
EventBus.getDefault()
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;
}
进入EventBus的getDefault方法我可以看到,EventBus使用了双重检验锁的单例模式 来确保线程安全,保证全局只有一个EventBus对象,具体的初始化操作我们接着往下看
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
。。。
mainThreadSupport = builder.getMainThreadSupport();
//创建了一个HandlerPoster对象:继承自Handler实现了Poster接口
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
//创建了一个后台提交对象:继承自Runable实现了Poster接口,调用线程池实现
backgroundPoster = new BackgroundPoster(this);
//与上面不同点在于:它是异步提交的实现的
asyncPoster = new AsyncPoster(this);
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
。。。 }
}
这里因为代码比较多,我只保留了我们需要关注的部分代码,DEFAULT_BUILDER只是创建了一个默认的EventBusBuilder(一般情况下我们不需要理会其中的方法,有一种情况例外:当我们使用EventBus3之后提供的订阅下标
来加快用户的注册,会调用到EventBusBuilder的installDefaultEventBus()方法来生成一个EventBus对象,关于订阅下标的用法,这里不准备做讲解)。下面介绍的是EventBus实现的三个Poster实现类,主要用于订阅方法的调起。
mainThreadPoster:指的是一个HandlerPoster对象,可以看出使用的是Handler机制,发送事件,调用订阅方法处理,对应于上一篇我们提到的threadMode=MAIN或者threadMode=MAIN_ORDERED的其中一种情况。
backgroundPoster:BackgroundPoster内部创建了一个子线程执行发送,调起订阅方法,对应于threadMode=BACKGROUND。
asyncPoster:AsyncPoster内部创建了一个子线程来执行发送,调起订阅方法,不同于的BackgroundPoster的是AsyncPoster是不同步的。
EventBus的注册我分为两部分,分别是订阅方法查找,订阅方法的遍历,代码较多,慢慢分析。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
//获取订阅类的所有订阅方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//遍历订阅类,存储订阅类,订阅方法,订阅方法参数集合等数据
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
可以看到首先是调用subscriberMethodFinder对象的findSubscriberMethods方法来获取订阅方法集合,然后循环遍历每一个订阅方法进行订阅。这里先具体看一下查找订阅方法里面的代码,西面的代码分析涉及到了上一篇文章我们提到的问题:为什么EventBus注册更快?
,在这之前先认识一下SubscriberMethodFinder类,通过类名就可以看出该类主要用于订阅方法的查找,我着重列出了该类下面两个Map集合,是因为该集合主要用于存储App的全部订阅类以及对应的订阅方法,不说不甘心啊,哈哈。
//key:订阅类,values:订阅类中订阅方法的集合列表
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
public static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE1 = METHOD_CACHE;
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE1.get(subscriberClass);
//!=null说明该类已经订阅过,直接返回订阅方法集合METHOD_CACHE
if (subscriberMethods != null) {
return subscriberMethods;
}
//默认情况下这里=false,表示是否强制使用反射查找方法
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//若订阅方法列表为null,表明订阅类中没有声明订阅方法,抛出异常
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//将订阅类 和订阅方法集合添加到METHOD_CACHE中,并返回订阅方法集合key:Class,values:list
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
首先查找是否存在该订阅类的订阅方法集合,若存在则直接返回。这里ignoreGeneratedIndex默认为 false,表示是否强制使用反射,除非主动设置,否则只会走findUsingInfo方法。若是subscriberMethods订阅方法集合为不存在,则直接抛出异常,之前说到订阅方法必须声明为public类型,在这里可以得到证明
,若不为null则添加到METHOD_CACHE集合中,并返回订阅方法。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
//使用订阅类对象初始化FindState中的成员变量
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 {
//通过反射获取FindState
findUsingReflectionInSingleClass(findState);
}
//判断是否存在原生父类:若存在继续遍历 反之calzz置null,退出循环
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
通过prepareFindState方法来生成一个FindState对象,这里比较有意思的是官方通过创建一个长度为4的数组来减少对象的 创建和销毁开销,(学到老啊,哈哈),
/**
*获得一个FindState对象
*/
private FindState prepareFindState() {
//从FIND_STATE_POOL数组中获取一个FindState对象,如不存在则直接创建
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
return new FindState();
}
然后初始化findState的内部成员变量,通过while循环来获取订阅方法,通过cheakAdd来检查该方法是否被添加过,未添加则存储在findState的subscriberMethods集合中。在EventBus3.0之后官提供了Subscriber Index(订阅下标)用于加快用户注册,这是一个可选操作(需要注意的是Subscribe不可以声明在匿名内部类中)。该新特性解决了使用反射带来的开销
。这里帮助我们理解了上一篇文章提到的EventBus为什么更快。若使用了该新特性则findState.subscriberInfo不为null,直接通过subscriberInfo.getSubscriberMethods获取订阅方法数组,并添加到findState对象的集合中。若不使用该订阅下标,则调用findUsingReflectionInSingleClass方法来获取订阅方法,该方法是通过反射来遍历订阅类中的所有方法,来查找订阅方法:
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;
}
//循环遍历Method 找出订阅方法
for (Method method : methods) {
int modifiers = method.getModifiers();
//获取Public类型方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
//获取参数个数为1的方法
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
//获取声明了Subscribe的方法
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");
}
}
}
反射的代码不准备多讲,如果对于反射的一些方法不熟悉的,推荐去了解一下,这里主要就是通过反射获取所有方法,根据方法的一些注解,修饰符和参数类型个数来确定是否是需要的订阅方法,这里主要注意两个异常:
Public:订阅方法必须是Public类型,否则直接报错
parameter:订阅参数个数必须有且只有一个,否则直接报错
下面看一下订阅方法查找第二部最后一段代码getMethodsAndRelease(findState)
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
//重置FindState中所有对象,集合
findState.recycle();
//将重置的FindState对象添加到FIND_STATE_POOL数组的null值处:主要是为了减少对象创建销毁的性能开销
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
获取订阅方法集合,并返回。这里将findState对象的变量置null,并将fingState对象地址传递给数组中第一个为null的对象。
通过上面的源码分析终于获取了订阅方法集合,接下来回到我们的最初订阅注册中,通过循环遍历每一个订阅方法,调用subscribe方法来讲需要的参数添加到不同的集合中。
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//获取订阅方法的参数类型
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//将订阅参数类型当成key 获取订阅方法集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//若集合为null表明还不存在该类型的订阅方法集合,则创建集合将参数类型和订阅方法当成key:values添加
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//若集合不为null且该类型在该订阅类中已经注册,则抛出异常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//循环遍历优先级搞得放置在list前面,若优先级相同,排在尾部
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;
}
}
//根据订阅类获取订阅类型集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
//不存在则创建一个集合,并添加
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//接下来是sticky粘性事件的处理
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).
//判断sticky粘性事件集合中是否存在该参数类型的粘性事件,找到并调用粘性订阅方法。
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);
}
}
}
将订阅类和订阅方法组合成一个封装类Subscription,接下来通过订阅参数从subscriptionsByEventType集合中获取Subscription封装类集合,若不为null且未被添加过,则根据优先级添加到subs criptionsByEventType集合中,这里就是EventBus优先级的作用之处,更高优先级的用户派在链表的前端,会首先被呼叫
。最下面的代码表明如果当前订阅方法是粘性方法,则首先从粘性事件集合中获取相同参数类型的集合,若存在则循环遍历并调起订阅方法。这里的代码看起来比较多,但比较好理解,且注释写的比较全就不细解了。
因为代码比较多,所以决定分为两篇来写源码的阅读。事件的发送,调用和解绑下一篇文章我会继续介绍道。