个人博客:haichenyi.com。感谢关注
之前我们讲过获取EventBus对象的源码,这一篇,我们来讲讲注册的源码。推荐EventBus 3.0进阶:源码及其设计模式 完全解析
简介
/**
* 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.
*/
翻译: 注册给订阅方去接收事件,订阅者一旦对接收事件不感兴趣了,就要unregister,订阅者必须要有用Subscribe注解的方法,注解也可以设置线程和优先级
白话文: 订阅者要是想接收消息,必须要先注册。当页面退出,或者不想接收消息的时候必须要反注册,不然他会一直处于接收消息的状态,页面退出会内存泄漏。订阅者的接收方法必须要用Subscribe注解,这个注解的后面可以设置接收这个消息的线程和优先级。如下:
@Subscribe(threadMode = ThreadMode.MAIN,priority = 100,sticky = true)
public void handleMsg(DataBean dataBean){
}
就像上面这样写,我一个一个来讲。我们先来说说这个ThreadMode类,点进去,我们可以看到如下内容:
/**
* 每个订阅的方法都有一个线程,决定那个线程的方法被叫做EventBus
* EventBus的线程可以跟Post事件的那个线程不相同
*/
public enum ThreadMode {
/**
*订阅者将在跟Post事件的那个线程的同一个线程中被调用,这是默认值,
* 因为,他没有线程切换,所以开销最少,所以也是推荐模式。需要注意的是
* post事件的线程可能是UI线程,也可能是其他线程,所以,这里的操作要做判断,
* 如果是UI操作,你必须要在UI线程中完成,如果是耗时操作,你必须要新开线程
*/
POSTING,
/**
* 在Android上面,订阅者将会在UI线程中调用,如果post事件的线程是UI线程,
* 辣么,这个订阅方法将直接被调用,如果不是UI线程,辣么,它将要排队交付,
* 所以,这里可能阻塞线程,订阅者使用这个模式必须要快速返回,避免阻塞UI线程,
* 就是不要在这里做耗时操作。谢谢。
*/
MAIN,
/**
*这一个,跟上面的刚好对应,就是不管怎么样,都要排队交付,
* 不论post事件是不是处于UI线程发送的
*/
MAIN_ORDERED,
/**
* 在android上面,订阅方法将在子线程中调用。如果post事件处于子线程,
* 辣么,订阅方法将直接被调用。如果post事件处于UI线程,辣么,eventBus
* 就会新开线程,按照顺序处理事件,当然,也要注意,避免阻塞子线程
*/
BACKGROUND,
/**
* 订阅方法将会在独立的线程中调用,这个线程总是独立语post事件
* 所处的线程和主线程。如果post事件是耗时操作:例如网络请求,
* 订阅方法调用的时候,不会等待。我们不用考虑线程数量的问题,
* EventBus已经限制了并发线程,并使用线程池高效的重用线程
*/
ASYNC
}
他就是一个枚举类,几个值的意义,我说的很清楚了。
我们再来讲讲另外两个: sticky,默认值是false,如果设置成true,辣么,这个事件将会是粘性事件。发送事件的方式从post变成了postSticky,其他都没变。
再来讲讲 priority ,默认值是0,在同一个线程中值越大,优先级越高。优先级高的比优先级低的先收到消息。
好,终于准备工作做完了,我们来看看 register() 方法
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
注册方法。首先,他通过反射的方式获得当前类名,然后通过当前类名,找到订阅方法,存到list里面。我们来看看 findSubscriberMethods()方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//首先从缓存中读取当前类的订阅方法,如果不等于null,就直接返回从缓存中读取到的list
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex的值,从Builder可知,一般为false。
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 {
//将获取的subscriberMeyhods放入缓存中
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
上面的注释写的很清楚,ignoreGeneratedIndex为false,辣么就会走findUsingInfo() 方法
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
//首先新建了一个FindState,FindState是一个静态内部类,保存订阅者的信息
FindState findState = prepareFindState();
//初始化FindState
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
//初始化的findState获得的订阅者信息,一般都是null
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);
}
上面,我们提到了FindState类,我们来看看这个类的代码
static class FindState {
//订阅方法的列表
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
//以class的名称为key,以方法为value
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
//以方法名称为key,订阅者类为value
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
final StringBuilder methodKeyBuilder = new StringBuilder(128);
Class<?> subscriberClass;
Class<?> clazz;
boolean skipSuperClasses;
SubscriberInfo subscriberInfo;
//初始化
void initForSubscriber(Class<?> subscriberClass) {
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}
}
不难看出,这里的几个map包括了,类名找方法,方法名找类,我们后面都用的到,然后就是初始化方法,前面我们注释里面写了,初始化之后一般信息都是null,这里我们也可以看到。所以,它会走 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;
}
/*------------------------------上面就是获取方法,重要的是在下面------------------------------------*/
//这里我强调的是我们前面的用法里面有说过注意点
//1.必须是public修饰
//2.必须是void类型
//3.必须是一个参数
//4.必须用Subscribe注解
for (Method method : methods) {
//获取方法的修饰符
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//获取方法参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
//如果参数个数等于1
if (parameterTypes.length == 1) {
//获取方法注解名称
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
//参数类型 即为事件类型
Class<?> eventType = parameterTypes[0];
//调用checkAdd方法判断是否添加过
if (findState.checkAdd(method, eventType)) {
//从注解里面获取线程模式
ThreadMode threadMode = subscribeAnnotation.threadMode();
//新建一个SubscriberMethod对象,并添加到findState的subscriberMethods这个集合内
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
//如果开启了严格验证,同时当前方法又有@Subscribe注解,对不符合要求的方法会抛出异常
} 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");
}
}
}
这个方法非常重要!!!在这个方法内部,利用反射的方式,对订阅者类进行扫描判断,是否满足条件从而找出订阅方法,并用上面的容器进行保存。辣么,上面提到的 checkAdd() 方法是怎么检查的呢?
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);
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);
}
}
这个注释写的很清楚,两层检验,第一层是检测事件类型,第二次检验则是检验判断方法的完整,首先以eventType为键,方法为值,存到map中(这个map是在FindState类初始化的),put方法会有一个返回值,返回value,这个value是这个key对应的上一个值,所以说,如果是第一次存放,那么就会返回null。否则,之前存放过,辣么就会进入下一个判断 checkAddWithMethodSignature
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);
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;
}
}
这个方法就是用来判断方法签名是否相同的,方法签名是什么呢?就是修饰符+返回类型+方法名+参数list是否相同。如果方法签名相同,辣么,就把旧值赋值给methodClassOld,判断这个值不是为null,第一次调用,没有旧值,就肯定为null,所以,if前面的一个条件是满足的,后面一个条件methodClassOld.isAssignableFrom(methodClass) 的意思是判断旧值是否是methodClass或者同一个类,如果两个条件都不满足,辣么当前方法就不会添加为订阅方法。
那么,说了一大堆关于checkAdd和checkAddWithMethodSignature方法的源码,那么这两个方法到底有什么作用呢?从这两个方法的逻辑来看,第一层判断根据eventType来判断是否有多个方法订阅该事件,而第二层判断根据完整的方法签名(包括方法名字以及参数名字)来判断。下面是笔者的理解:
第一种情况:比如一个类有多个订阅方法,方法名不同,但它们的参数类型都是相同的(虽然一般不这样写,但不排除这样的可能),那么遍历这些方法的时候,会多次调用到checkAdd方法,由于existing不为null,那么会进而调用checkAddWithMethodSignature方法,但是由于每个方法的名字都不同,因此methodClassOld会一直为null,因此都会返回true。也就是说,允许一个类有多个参数相同的订阅方法。
第二种情况:类B继承自类A,而每个类都是有相同订阅方法,换句话说,类B的订阅方法继承并重写自类A,它们都有着一样的方法签名。方法的遍历会从子类开始,即B类,在checkAddWithMethodSignature方法中,methodClassOld为null,那么B类的订阅方法会被添加到列表中。接着,向上找到类A的订阅方法,由于methodClassOld不为null而且显然类B不是类A的父类,methodClassOld.isAssignableFrom(methodClass)也会返回false,那么会返回false。也就是说,子类继承并重写了父类的订阅方法,那么只会把子类的订阅方法添加到订阅者列表,父类的方法会忽略。
让我们回到findUsingReflectionInSingleClass方法,当遍历完当前类的所有方法后,会回到findUsingInfo方法,接着会执行最后一行代码,即return getMethodsAndRelease(findState);那么我们继续 getMethodsAndRelease
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
//从findState获取subscriberMethods,放进新的ArrayList
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
//把findState回收
findState.recycle();
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;
}
通过该方法,把subscriberMethods不断逐层返回,直到返回EventBus#register()方法,最后开始遍历每一个订阅方法,并调用subscribe(subscriber, subscriberMethod)方法,那么,我们继续来看subscribe方法。
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//将subscriber和subscriberMethod封装成 Subscription
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//根据事件类型获取特定的 Subscription
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//如果为null,说明该subscriber尚未注册该事件
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果不为null,并且包含了这个subscription 那么说明该subscriber已经注册了该事件,抛出异常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//根据优先级来设置放进subscriptions的位置,优先级高的会先被通知
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;
}
}
//根据subscriber(订阅者)来获取它的所有订阅事件
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//下面是对粘性事件的处理
if (subscriberMethod.sticky) {
//从EventBusBuilder可知,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<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 {
//根据eventType,从stickyEvents列表中获取特定的事件
Object stickyEvent = stickyEvents.get(eventType);
//分发事件
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
到目前为止,注册流程基本分析完毕,丢一张流程图
注册流程图.png
网友评论