EventBus源码赏析三 —— 订阅信息查找

654 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

EventBus.register()方法中通过findSubscriberMethods()方法获取了订阅者的所有的订阅方法,findSubscriberMethods是SubscriberMethodFinder类中的方法,SubscriberMethodFinder类由EventBus类创建,构造方法如下

SubscriberMethodFinder(
 List<SubscriberInfoIndex> subscriberInfoIndexes,
 boolean strictMethodVerification,
 boolean ignoreGeneratedIndex) {     
}
  • subscriberInfoIndexes 注解生成的索引
  • strictMethodVerification 反射获取订阅方法的时候遇到非法情况是否抛出异常
  • ignoreGeneratedIndex 是否忽略索引,强制使用反射获取订阅信息

查找订阅方法

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> 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;
    }
}

EventBus3.x提供了两种途径查找订阅方法,一个是运行时反射,一个是注解处理器,我们可以通过设置ignoreGeneratedIndex决定使用那种,由源码可以看出,如果没有找到订阅方法就会抛出异常, 所以订阅类或者其父类必须提供一个公开的被@Subscribe注解修饰的方法。

无论使用哪种途径获取订阅方法,都是一个耗时过程m所以EventBus提供了缓存池METHOD_CACHE,如果池中已经存在当前订阅者的信息,则直接返回,否则获取了之后放入池中。

运行时反射

通过反射获取订阅方法

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findUsingReflectionInSingleClass(findState);
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

FindState类是订阅方法的一个临时容器,它包含了订阅类和其订阅方法等临时信息。由于使用比较频繁,性能开销比较大,所以使用了数据池优化。

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();
}

EventBus提供了一个大小为POOL_SIZE(4)的FindState池(FIND_STATE_POOL),优先从池中获取,没有则直接创建。获取完成之后调用initForSubscriber初始化订阅者的信息。 然后通过findUsingReflectionInSingleClass()查找订阅方法。

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        try {
            methods = findState.clazz.getMethods();
        } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
            //省略异常日志
        }
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        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");
        }
    }
}

这里使用了getDeclaredMethods()和getMethods()两种方法获取Methods数组,他们的区别如下:

  • getDeclaredMethods() 获取当前类非继承的所有方法
  • getMethods() 获取当前类以及父类,接口的所有public方法

由于getDeclaredMethods()的效率更高,所以优先使用它获取,如果失败了再通过getMethods()获取,而getMethods()本身就会获取父类以及接口的public方法,所以在获取成功之后就没必要再去父类查找了,所以设置findState.skipSuperClasses = true。

查找到Method数组之后,筛选这些Method,获取我们需要的

  • (modifiers & Modifier.PUBLIC) != 0 方法必须是公开的
  • (modifiers & MODIFIERS_IGNORE) == 0 方法必须是非静态的,非抽象的,以及不是编译过程中添加的
  • parameterTypes.length == 1 方法必须只有一个参数
  • subscribeAnnotation != null 方法必须有@Subscribe注解

满足以上条件的Method不一定是我们需要的结果,还需要通过checkAdd()方法检查下

boolean checkAdd(Method method, Class<?> eventType) {
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                throw new IllegalStateException();
            }
            anyMethodByEventType.put(eventType, this);
        }
        return checkAddWithMethodSignature(method, eventType);
    }
}

EventBus首先通过事件类型eventType在anyMethodByEventType中查找当前类是否添加了对eventType类型处理的方法,如果没有,则认为是有效的订阅方法,否则通过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)) {
        return true;
    } else {
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

这里计算方法的特征签名比较简单,直接用“methodName>eventTypeName”拼接,如果该签名的方法没有出现过(methodClassOld == null),则认为是有效的订阅方法,如果出现了,就判断已经添加过的订阅方法所在的类(methodClassOld)是否为当前订阅方法所在类(methodClass)的父类或者接口(methodClassOld.isAssignableFrom(methodClass))。如果是,说明子类重写了该方法,也认为是有效的,否则返回false并记录。

找到合法的Method之后,收集信息,添加到容器findState当中,如果找到非法的Method,根据strictMethodVerification和其他参数决定是否抛出异常。

当前类的订阅方法查找完成之后,还需要查找父类,通过moveToSuperclass改变查找的class

void moveToSuperclass() {
    if (skipSuperClasses) {
        clazz = null;
    } else {
        clazz = clazz.getSuperclass();
        String clazzName = clazz.getName();
        if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
            clazz = null;
        }
    }
}

这里有两点细节优化了查找效率

  • 上面提到使用getMethods()本身就会获取父类以及接口的public方法,然后设置skipSuperClasses = true。所以这里一开始就判断skipSuperClasses,如果为true就设置clazz=null
  • 跳过系统类,这些类没必要解析

查找完成之后,获取到的信息都保存在了FindState中,通过getMethodsAndRelease()获取订阅信息

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    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;
}

getMethodsAndRelease做了两件事

  1. 返回订阅方法subscriberMethods
  2. 回收findState,先清空,然后放到FIND_STATE_POOL缓存中,至此FindState有取有放,形成了闭环

注解处理器

通过注解处理器获取订阅方法

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    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 {
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

可以看到,通过注解处理器获取订阅方法流程与反射获取大体一致,先使用getSubscriberInfo()查找订阅信息,如果查找为null,降级使用反射查找

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;
}

第一次进入getSubscriberInfo()方法findState.subscriberInfo是空的,所以先从subscriberInfoIndexes中查找SubscriberInfo,查找完当前类之后,通过moveToSuperclass(),改变findState.clazz然后继续查找父类的SubscriberInfo,关于subscriberInfoIndexes,他是通过EventBusBuilder的addIndex()方法添加的,至于如何添加,这涉及到对 @Subscribe 注解的解析,将在后面EventBusAnnotationProcessor中提到。

经过上面两种途径获取到List后将回到EventBus类由subscribe()方法继续处理。