EventBus3.x源码分析之注册(一)
官方链接:https://github.com/greenrobot/EventBus
官方图例:
主要流程:
发布者通过post发送事件,EventBus将事件传递给提前注册好的订阅类。当然分发和处理事件可以指定不同线程,可以给订阅者设置优先级,可以设置粘性事件。索引方式提高性能等。
EventBus的成员:
EventBus {
static volatile EventBus defaultInstance;
//EventBus功能构建器
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
//以当前事件类为key,存放事件,包括事件的父类,接口类的集合
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
//以订阅方法的参数类型为key,存放订阅类和订阅方法的subscription集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//以订阅类为key,存储所有的订阅方法参数类型。
private final Map<Object, List<Class<?>>> typesBySubscriber;
//以事件class为key,Object为value存入,保存粘性事件(可以实现先发送后注册的事件)
private final Map<Class<?>, Object> stickyEvents;
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
//以"空间换时间"的方式为每个线程提供一份实例,保证数据安全速度够快:
return new PostingThreadState();
}
};
private final MainThreadSupport mainThreadSupport;//提供线程,和事件分发器
private final Poster mainThreadPoster;//主线程事件分发器
private final BackgroundPoster backgroundPoster;//子线程事件分发器
private final AsyncPoster asyncPoster;//异步事件分发器
private final SubscriberMethodFinder subscriberMethodFinder;//可以理解为订阅方法的管理类
private final ExecutorService executorService;//提供线程池
//构建者中的一些配置
private final boolean throwSubscriberException;
private final boolean logSubscriberExceptions;
private final boolean logNoSubscriberMessages;
private final boolean sendSubscriberExceptionEvent;//反射发生异常时,是否发送异常事件
private final boolean sendNoSubscriberEvent;//如果没有通过事件找到对应的订阅信息,是否发送该事件
private final boolean eventInheritance;//是否需要发送事件的父类或接口类,默认true
//通过addIndex添加的事件的索引
private final int indexCount;
}
1.注册:
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(subscriberClass);会去获取所有的符合条件的订阅方法
SubscriberMethodFinder#
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略注册时通过addIndex方法添加的订阅信息(EventBus3.0以后强大的性能优化)
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 {
//以订阅类名为key,订阅类中的订阅方法为value存入订阅方法的缓存容器(同步安全效率高的Hasmap,采用分段锁技术)中。
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
1、这个类维护了一个以subscriber(订阅类)为key,subscriberMethods(订阅方法)为value的缓存容器,目的是为了再次注册时直接将该订阅类中的订阅方法返回。
2、ignoreGeneratedIndex:是可以从EventBusBuilder中配置的boolean类型字段,代表是否忽注册通过EventBusBuilder中addIndex方法添加的订阅信息。默认不忽略,会走findUsingInfo。但是这次我们只分析findUsingReflection。
注:这里属于EvetBus新特性利用注解方式编译时生成订阅信息,无需运行时查找订阅类中的方法,主要功能在meta包和annotationprocessor包中,下篇文章会总结~。
SubscriberMethodFinder#
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private FindState prepareFindState() {
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();
}
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//在运行时添加方法
findUsingReflectionInSingleClass(findState);
//到父类中继续查找
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
prepareFindState()此方法维护了一个长度为4的findState的对象缓存池。这个findState就是保存了订阅者和订阅信息的封装类。
SubscriberMethodFinder#
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();
//这种获取方式不会获取父类的public方法,所以要去父类中查找并添加订阅方法(就要去父类中找就要执行moeToUperclass)
//但是父类中如果找到了被重写的public方法,还是会走methodClassOld.isAssignableFrom(methodClass)也会返回false防止再添加父类中的订阅方法。
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
//这种获取方式会获取到该类所有的public方法包括父类的public方法,但如果是重写的方法,就会获取重复
//但是只需要一个订阅方法即可,所以会有methodClassOld.isAssignableFrom(methodClass)也会返回false防止再添加父类中的订阅方法。
//但是在下一次moveToSuperclass方法中,相当于又在遍历父类,所以需要将tag置为true跳过。
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
//通过修饰符筛选,只支持public,且不支持abstract,static等
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");
}
}
}
1、这里的篇幅较长,目的是为了找到findState.class(订阅类)中的订阅方法
2、对满足条件的方法(如需要是public,但不是abstract,static等;只能有一个参数;带有@Sbscribe注解)找到,然后通过findState.checkAdd(method, eventType)进行校验,找寻完毕将订阅方法添加到订阅方法列表中。
注:在查找所有方法时有两种方式getDeclaredMethods()和getMethods(),也是EventBus在完善https://github.com/greenrobot/EventBus/issues/149这个issue的时候提供的解决方案,这里我也给出了比较详细的注释~
FindState#
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
boolean checkAdd(Method method, Class<?> eventType) {
// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
// Usually a subscriber doesn't have methods listening to the same event type.
Object existing = anyMethodByEventType.put(eventType, method);
//如果anyMethodByEventType没有该eventType类型的key返回true
if (existing == null) {
//不存在该参数类型的方法,直接添加到订阅者
return true;
} else {
//存在相同的参数类型,代表参数类型相同,但可能方法名不同,或者方法名也相同(父类中的被重写的方法)
if (existing instanceof Method) {
if (!checkAddWithMethodSignature((Method) existing, eventType)) {
// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
1、anyMethodByEventType这个map通过参数类型将方法保存,目的是为了排除相同的订阅方法。
2、官方注释这里有两层校验,一层是参数类型校验,另一层是方法签名校验;其实多数订阅者不会出现相同参数类型,不同方法名。
3、第一层校验通过直接返回true(代表没有找到相同的参数类型的方法,代表该方法可被订阅),否则(代表出现了相同的参数类型,不同给的方法名)进行少数的第二层校验checkAddWithMethodSignature((Method) existing, eventType)
FindState#
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
//所以再次校验需要用到方法名&参数类型名来作为签名
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
//找到相同的签名之前添加的方法所在类
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
//找不到说明为参数类型相同,方法名不同;
//找到了,说明存在了都相同的情况,这种时子父类情况,而methodClassOld.isAssignableFrom(methodClass)也会返回false。
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
1、subscriberClassByMethodKey这个map的作用同anyMethodByEventType,为了排除相同方法签名的订阅方法。
2、这里利用StringBuilder将方法名&参数类型名作为方法的签名,会出现一个问题就是如果该订阅类存在父类,那么可能出现相同的签名,这种情况需要排除掉父类中的订阅方法;如methodClassOld == null代表不是继承的方法,只有订阅类中存在直接添加即可,但是如果methodClassOld!=null,代表出现了方法签名相同的订阅方法,这个方法就是父类中被重写的那个方法,当然该方法不能被添加。所以通过methodClassOld.isAssignableFrom(methodClass)返回false略过。
注:这里的methodOld返回的一定是子类,因为查找方法从子类开始,所以子类一定不是父类的当前类或父类,所以返回false。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//在当前类和自定义的父类中找不到订阅方法了为止
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
//从通过addIndex方法,添加的订阅信息找到并添加
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);
}
此时回到findUsingInfo这个方法,走了一圈后说明,
1、如果找类中方法通过getDeclaredMethods找到的就要从父类中继续查找,因为它找不到父类中的public方法;
2、但是如果通过getMethods找到的包括了父类中的public,所以有设置findState.skipSuperClasses = true;
FindState#
void moveToSuperclass() {
//跳过父类
if (skipSuperClasses) {
clazz = null;
} else {
//不跳过父类
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
/** Skip system classes, this just degrades performance. */
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
clazz = null;
}
}
}
通过getDeclaredMethods找方法会不断的向上层查找知道找到不是自己定义的(系统的父类)为止.
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
//将findstate清空,并将订阅者方法返回
findState.recycle();
System.out.println(subscriberMethods.toString());
synchronized (FIND_STATE_POOL) {
//这里做了一个findState的缓存大小为4
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
最后把符合条件的方法都添加到了findState中的findState.subscriberMethods集合中。最后通过getMethodsAndRelease(FindState findState)将订阅方法列表返回。
以上的整个流程就是非粘性事件注册,粘性事件会在后面的总结中说明。
梳理整个流程:
就是通过当前的订阅类找到订阅类中的所有方法,进行筛选,筛选出public 非static非abstract,只有一个参数类型,带有@Subscribe注解的方法,并进行非重订阅校验。验证通过后会返回一个订阅列表然后开始订阅。
1.订阅
就是将订阅方法通过参数类型(事件类型)为key进行封装,等待post事件的时候,将订阅方法取出回调。
1.非粘性订阅
EventBus#
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//包装了订阅类和订阅方法
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//通过参数类型获取subscription集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//为null说明不存在该参数类型的方法没有被订阅过
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//subscriptions不为null的情况是指方法名不同,但是参数类型相同,同一个key将上次的存储的取出来了。
//一个类不可被重复订阅,注:这里的contains方法会走到Subscrptions的equals方法,最终比较的是是否存在相同的订阅类名,方法名,参数类型名。
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//如果长度不为0代表有大于1个的不同方法名,相同参数名的方法
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;
}
}
//以订阅类为key,存储所有的订阅方法参数类型。维护此map是为了进行反注册
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
...
}
1、subscriptionsByEventType这个map的作用主要就是用于在post事件的时候,根据事件类型,将订阅方法取出回调。
2、typesBySubscriber这个map的作用是用于在反注册时,通过当前订阅类为key,将订阅信息进行反注册。
3、subscriptions这个List用做储存订阅信息。可以通过事件类型从subscriptionsByEventType取出。
注:在进行获取订阅列表进行订阅方法添加的时候可以给SubscriberMethod(订阅方法包装类)设置三个属性ThreadMode threadMode; int priority; boolean sticky;这里用到了其中一个就是priority。
分析代码,如果订阅列表中没有订阅方法,直接添加;但是如果之前有订阅方法,就会将priority大的订阅方法添加到最前面,保证被先处理。
2.粘性订阅
有一种需求是你想要提前发送事件,等一旦注册马上处理该事件。
同样,在进行获取订阅列表进行订阅方法添加的时候可以给SubscriberMethod(订阅方法包装类)设置三个属性ThreadMode threadMode; int priority; boolean sticky;这里用到了其中一个就是sticky。
如果订阅方法支持sticky
EventBus#
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
...
//订阅方法是否支持粘性
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>).
//这里可以实现先通过#postSticky(Object event)发送事件在进行订阅、
//而且一旦注册马上事件马上进入队列或处理
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);
}
}
}
普通订阅都是在订阅好以后,postStickey(Object object)发送事件,然后执行回调;
粘性订阅是指利用postStickey(Object object)发送事件:
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这个map会在发送事件前维护一个粘性事件列表,在刚订阅的时候直接发送事件,就行处理。
2.反注册
EventBus#
public synchronized void unregister(Object subscriber) {
//通过当前订阅类找出之前保存的额eventTypes集合
List<Class<?>> 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());
}
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
//看subscriptionsByEventType里面的subsciption中是否存在当前的subscriber
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
订阅时已经通过订阅类维护了一个订阅方法类型的列表,通过这个列表中的元素可以在subscriptionsByEventType找到对应的订阅信息,并进行反注册。
以上就是EventBus的注册流程。