EventBus源码解析之注册

  • 前言

    前面介绍了Eventbus的构造器,部分参数以及属性在注册时候也会用到。所以了解构造函数中Build的各个属性对后续也是有颇多帮助。有关于粘性事件的处理涉及到了事件分发,所以移到后续的EventBus之事件分发中分析。

  • 源码解析

    1、概括介绍

    首先注册的总入口还是在Eventbus.java文件里。

    注册分为两个步骤:第一步是根据类的类型查询符合条件的所有方法,第二步是订阅遍历到的信息。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = 	     subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    

    而findSubscriberMethods又可以分为通过反射查询和通过索引查询。整体逻辑如下图所示:
    在这里插入图片描述

    2、查询Methods

    现在先分析第一步findSubscriberMethods这个方法,跟到这个方法的实现可以看到。一个是定义了个METHOD_CACHE这个MAP类用于缓存搜索的方法,查询的时候先判断该类是否已经存在避免重复查询。另一个代码就是根据前面提到构造方法里面配置的ignoreGeneratedIndex来判断是通过反射还是索引来寻找方法。

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
            //METHOD_CACHE用于缓存现有的订阅者。关键字是类
            List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
            if (subscriberMethods != null) {
                return subscriberMethods;
            }
    
            //根据ignoreGeneratedIndex来判断是否利用索引还是利用反射获取方法
            //利用索引的效率可以提高3~6倍
            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;
            }
        }
    
    2.1 通过反射查询
    2.1.1 整体分析

    先分析一下findUsingReflection,该方法顾名思义是通过反射来获取所有的方法。OK,下面来看下该方法:

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        //从state池中取出一个state,然后见取出来的位置清空
        FindState findState = prepareFindState();  //标注1
        //做一些初始化工作,将subscriberClass赋值给的clazz类
        findState.initForSubscriber(subscriberClass);
        //遍历包括父类在内的所有类
        while (findState.clazz != null) {//标注2
            //利用反射更新findState.subscriberMethods中的值
            findUsingReflectionInSingleClass(findState);
            //跳到父类
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);//标注3
    }
    

    代码里面分成两部分,第一个部分是标注1和标注3的的代码。他们是创建一个长度为4的对象池,当开始查询的时候从线程池中取一个FindState对象出来,而查询完毕之后把该变量清空之后放回对象池。这是考虑到注册的时候有可能会出现大量的创建对象耗费资源,对象池重复使用有效的规避了这类问题。然后就是第二部分循环遍历包括非系统的所有父类的方法并存入findstate中。

    2.1.1 反射解析数据

    OK,下面来看下真正的查询功能方法findUsingReflectionInSingleClass(findState),代码如下所示:

    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities//2
            methods = findState.clazz.getDeclaredMethods();//标注1
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {//标注2
            //getModifiers用于返回该字段的修饰符
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 由此Method对象表示的方法的形式参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                //参数只允许一个。使用该内核的时候必须遵守的规则
                if (parameterTypes.length == 1) {
                    //获取指定类型的注释。该方法返回的是Subscribe.class类型的注释
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    //如果该类不为空。那么把该方法的各种信息存入findState.subscriberMethods中去。
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        //通过method, eventType检测是否要增加到Map组中去。
                        if (findState.checkAdd(method, eventType)) {
                            //获取该注解的threadMode类型,即线程类型。
                            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,注释还特意写了:getDeclaredMethods会比getMethods快,特别对于像Activity这种内容比较多的类。先看下两者的区别:

    public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
    public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。

    看到一篇博客提到这是考虑到EventBus会遍历父类,论点是getDeclaredMethods是不包括继承方法,这样就避免了重复查询父类的方法从而造成浪费。此时心生一个疑问:既然getMethods()可以查询所有方法了,为什么还要查询父类,这个论点好像有点站不住脚。

    那么重新思考下作者给出的提示,结合标注2处的注释以及getDeclaredMethods以及getMethods.我们可以想象一个场景如果一个自定义的Activity子类有两个自定义public方法。有两种方案:

    A方案:用getDeclaredMethods的话,那么只需查询这两个自定义方法,后续查询父类Activity的方法时,作者直接把他当成系统类直接屏蔽。所以最终只查询了两个方法。

    B方案:用getMethods的话,会把这两个自定义方法以及Activity的几十个public方法都查询一遍。

    Activity这个类是系统的,查询就是多此一举。很明显A方案会更高效而准确。再看看源码A方案报出异常的时候才使用B方案。

    try {
        	//A方案
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
        	//B方案
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
    

    至于为什么有这个报错。作者提供的FAQ说的很清楚了。下面截取了其中的几个回答:

    http://greenrobot.org/eventbus/documentation/faq/中提到:

    The exception is thrown if the class has methods with a parameter that
    is unavailable to the API level of the device. For example, the class PersistableBundle was added in API level 21.

    https://github.com/greenrobot/EventBus/issues/149中提到:

    SubscriberMethodFinder#findSubscriberMethods used to use Class#getMethods(), which doesn’t check method signatures (for things such as argument types that don’t exist). This was changed to use Class#getDeclaredMethods(), which does check, and throws an exception if something goes wrong.

    See lines 763, 770, and 822 in https://android.googlesource.com/platform/libcore/+/a8a489492749b845cc2a85d67c664449d7b0b019/libart/src/main/java/java/lang/Class.java.

    大概意思就是getMethods不对方法进行检查而getDeclaredMethods会检查方法,所以使用getMethods()并不会报错也证明了方案B的可行性,而getDeclaredMethods报错正是因为编译时候的版本比机器中的API版本高,某些方法不匹配造成的报错。所以,先执行方案A有报错再执行方案B。OK,接下来的大概流程就是筛选查询出来的方法以及获取注解的配置并存储到findState.subscriberMethods中。这其中其他都比较容易理解,注意到这里面有个findState.checkAdd(method, eventType)方法比较难理解.

    2.1.3 校验method以及参数类型

    现在开始分析findState.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.
        //Map里的put方法,如果key值不存在,则返回值是null,但是key值如果存在,则会返回原先被替换掉的value值.
        Object existing = anyMethodByEventType.put(eventType, method);
        //如果原先eventType对应的方法为空的话,则返回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);
        }
    }
    
    private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
        methodKeyBuilder.setLength(0);
        methodKeyBuilder.append(method.getName());
        methodKeyBuilder.append('>').append(eventType.getName());
    
        //method.getName()+">"+eventType.getName(),把这个作为关键字,Class作为值
        String methodKey = methodKeyBuilder.toString();
    
        //getDeclaringClass:得到目标方法所在类对应的Class对象
        Class<?> methodClass = method.getDeclaringClass();
        Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
        //methodClassOld与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;
        }
    }
    

    checkAdd根据注释可以看到,他有两层检测。第一级是检测eventType关键字(一般情况下订阅者对于eventType只会定义一个处理方法),如果还没定义过则直接返回true。第一级不为空则进行第二级检测,第二级检测是checkAddWithMethodSignature方法中利用methodKey为关键字(方法名加参数名)。一个关键字只会对应一个类。于是乎就可以获取对应类并检测当前是否属于父类,如果是父类这屏蔽掉。注意到其中有个检测:

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

    具体作用也是百思不得其解,Github看到一个问答是抛出IllegalStateException异常:https://github.com/greenrobot/EventBus/issues/539。给出的答复是要求最好是把订阅者定义在子类中,估计是因为定义在父类中没作用也浪费查询的时间。

    OK,到这边通过反射方式注册订阅者的方法就介绍完了。

    2.2 通过索引查询
    2.2.1 整体分析

    下面介绍另个分支利用索引方式查询,比较简单。先把代码贴上来:

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {//标注1
            findState.subscriberInfo = getSubscriberInfo(findState);//标注2
            //如果索引为空,那么利用反射找到所有的方法
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();//标注3
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
    

    步骤和通过反射获取方法大部分一样,在于标注1这个循环对于Methods获取方式有所不同。而差异之处主要集中在标注2和标注3这两个地方。

    首先看下标注2,下面是getSubscriberInfo的方法

    private SubscriberInfo getSubscriberInfo(FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {//标注1
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {//标注2
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);//标注3
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }
    

    可以看到有两个if语句。一个是标注1。这个暂时先不看,因为findState.subscriberInfo这个正是本方法才会返回的,第一次肯定为空不会跑进来。先看下标注2注意到subscriberInfoIndexes这个变量,是构造的时候也就是调用者通过Builder传进来的。传进来的索引类是利用Gradle自动生成的(具体用法参考官方)。OK,顺着标注2看下去,可以看到标注3有个获取SubscriberInfo对象的方法,这个正是关键,下面把我那个自动生成的类贴出来。

    /** This class is generated by EventBus, do not edit. */
    public class MyEventBusIndex implements SubscriberInfoIndex {
        private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    
        static {//标注1
            SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    
            putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMain.class,
                    true, new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("onEventMainThread", TestEvent.class, ThreadMode.MAIN),
            }));
            ....
        }
    
        private static void putIndex(SubscriberInfo info) {//标注2
            SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
        }
    
        @Override
        public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {//标注3
            SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
            if (info != null) {
                return info;
            } else {
                return null;
            }
        }
    }
    

    结合前面的getSubscriberInfo代码里可知标注3正是获取SubscriberInfo的具体实现。而标注1在新建对象的时候就执行了SUBSCRIBER_INDEX数据的填充。所以SUBSCRIBER_INDEX里面已经获取到了所有的SubscriberInfo对象。获取到方法之后,返回到上面的findUsingInfo的标注3处可以看到是通过getSubscriberMethods来获取SubscriberInfo中的具体所有方法。

    到这里我们了解到索引是通过回调EventBus自动生成的索引类的getSubscriberInfo方法来查询信息的!

    而MyEventBusIndex获取的是SimpleSubscriberInfo类型的那么就有必要分析下SimpleSubscriberInfo这个类!!

    2.2.2SimpleSubscriberInfo类

    相关类位于meta目录中,这个目录估计是专门用来自动索引的,怎么生成索引以后有时间再撸代码。

在这里插入图片描述

可以看到getSubscriberInfo的标注1的findState.subscriberInfo.getSuperSubscriberInfo()实际调用的是AbstractSubscriberInfo中的getSuperSubscriberInfo。下面把AbstractSubscriberInfo的部分方法调出来:

protected AbstractSubscriberInfo(Class subscriberClass, Class<? extends SubscriberInfo> superSubscriberInfoClass,boolean shouldCheckSuperclass) {
        this.subscriberClass = subscriberClass;
        this.superSubscriberInfoClass = superSubscriberInfoClass;
        this.shouldCheckSuperclass = shouldCheckSuperclass;
    }

@Override
public SubscriberInfo getSuperSubscriberInfo() {
    if(superSubscriberInfoClass == null) {
        return null;
    }
    try {
        return superSubscriberInfoClass.newInstance();
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode,
                                                      int priority, boolean sticky) {
        try {
            Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
            return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
        } catch (NoSuchMethodException e) {
            throw new EventBusException("Could not find subscriber method in " + subscriberClass +
                    ". Maybe a missing ProGuard rule?", e);
        }
    }

superSubscriberInfoClass个是由SimpleSubscriberInfo传给AbstractSubscriberInfo的。那么看下SimpleSubscriberInfo类:

public class SimpleSubscriberInfo extends AbstractSubscriberInfo {

    private final SubscriberMethodInfo[] methodInfos;

    public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
        super(subscriberClass, null, shouldCheckSuperclass);
        this.methodInfos = methodInfos;
    }

    @Override
    public synchronized SubscriberMethod[] getSubscriberMethods() {
        int length = methodInfos.length;
        SubscriberMethod[] methods = new SubscriberMethod[length];
        for (int i = 0; i < length; i++) {
            SubscriberMethodInfo info = methodInfos[i];
            methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
                    info.priority, info.sticky);
        }
        return methods;
    }
}

注意到这边写死了一直为null,那么第一点:SubscriberInfo.getSuperSubscriberInfo()一直为null。

第二点:getSubscriberInfo中的标注3的getSubscriberMethods方法调用的是SimpleSubscriberInfo的getSubscriberMethods方法,而createSubscriberMethod掉用的是AbstractSubscriberInfo的createSubscriberMethod方法。他的作用是将SubscriberMethodInfo转成SubscriberMethod并返回。

2.2.3 结论

得出这两个结论返回findUsingInfo的代码块中:标注2获取的是类对应的封装好的信息,标注3是将解析出该信息的所有方法存入表中也就是注册。整体的流程也就是先调用gradle自动生成的索引获取封装信息,再解析保存封装信息中的方法,最后跳到父类。这个过程是循环的,直到碰到系统类,这样就获取了保存了所有的方法。那么findUsingInfo就理解完了。

OK,这两个查询解释完了,下面就开始分析注册了。

3 注册

可以看到是订阅查询出来的subscriberMethods。

synchronized (this) {
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod);
    }
}

那么就先分析一下subscribe这个方法:

// Must be called in synchronized block
   //注册订阅者。关键的两个参数:一个是订阅者本身,一个是订阅方法
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
       Class<?> eventType = subscriberMethod.eventType;
       Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
       CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//标注1
       if (subscriptions == null) {
           subscriptions = new CopyOnWriteArrayList<>();
           subscriptionsByEventType.put(eventType, subscriptions);
       } else {
           if (subscriptions.contains(newSubscription)) {//标注2
               throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);
           }
       }

       //根据订阅者优先级将newSubscription插入到subscriptions中去
       int size = subscriptions.size();
       for (int i = 0; i <= size; i++) {//标注3
           if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
               subscriptions.add(i, newSubscription);
               break;
           }
       }

       //将eventType加入subscribedEvents队列       
   List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
       if (subscribedEvents == null) {//标注4
           subscribedEvents = new ArrayList<>();
           typesBySubscriber.put(subscriber, subscribedEvents);
       }
       subscribedEvents.add(eventType);

       //注册的时候将粘性事件执行      
   if (subscriberMethod.sticky) {//标注5
     	...
   }

标注1是从subscriptionsByEventType中获取eventType为关键字的所有订阅者,如果没有则新建一个对象。接下来标注3是根据priority来判断插入的位置。如果是没定义的话默认为0那么在尾部插入,如果是有定义个数值的话按照数字大小插入,这个很好理解。

有一个需要注意的点就是标注2,先说说我碰到过的一个错误:如果父类和子类注册了拥有着相同方法名和参数的方法,那么就会报这个错误。回头看看这个报错,那么就是新注册的newSubscription在subscriptions里面已经存在就会报错。也就是说拥有着相同方法名和参数的方法的父类不能注册。

接下来就是看看怎么判断Subscription是相同的呢。由于方法名+参数和类是一一对应的关系,可以看到通过Subscription类重写hashCode来实现。

@Override
//用于对比两个对象是否一致
public int hashCode() {
    return subscriber.hashCode() + subscriberMethod.methodString.hashCode();
}

看下标注4将typesBySubscriber,顾名思义以Subscriber为关键字存储types。不过只看到了一个用处,就是判断Subscriber是否已经被注册了。标注5为粘性事件,这个可以放到下一个事件分发里面一起解析。

  • 总结

    1、有关Gradle自动生成索引的知识还不是很清楚其中方法,感觉很吸引人的一个功能。

    2、CopyOnWriteArrayList有必要可以撸一遍用法以及与其他List区别。

    3、用MainThreadSupport作为代理获取HandlerPoster,这个还不是特别清楚作用。

    4、Eventbus一直在强调在父类里面不要订阅方法,很多BUG会因此而出现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值