美文网首页
EventBus源码解读上篇:注册与订阅

EventBus源码解读上篇:注册与订阅

作者: 一线游骑兵 | 来源:发表于2019-01-03 15:46 被阅读5次

    目的:

    1. 分析EventBus调用register与unRegister做了哪些操作。
    2. 分析如何通过注解进行事件的订阅。
    EventBus.getDefault().register(subscriber):

    先看一下getDefault()方法:

        public static EventBus getDefault() {
            if (defaultInstance == null) {
                synchronized (EventBus.class) {
                    if (defaultInstance == null) {
                        defaultInstance = new EventBus();
                    }
                }
            }
            return defaultInstance;
        }
    
        public static EventBusBuilder builder() {
            return new EventBusBuilder();
        }
    
        /**
         * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
         * central bus, consider {@link #getDefault()}.
         */
        public EventBus() {
            this(DEFAULT_BUILDER);
        }
    

    典型的DCL单例模式。最终使用默认的Builder来构建一个EventBus(Builder模式)。
    需要注意的一点就是每个EventBus实例是相互独立的,bus1发送的事件不会跑到bus2注册的方法里边。

    下边来看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);
                }
            }
        }
    

    方法说明:

    1. 找到该订阅类(subscriber)中所有订阅方法(使用@Subscribe注解的方法,并将该方法的参数类型,方法名,所在线程,优先级,是否为粘性等信息封装到SubscriberMethod中)。【如何找到后边会专门解析】
    2. 轮询并将订阅者---订阅方法一一对应起来(通过subscribe函数)

    在看subscribe函数之前,要明白几个集合概念:

        //事件类型---订阅者列表 的映射类。 如A、B两个类都订阅了Type1的方法,则该集合为Type1--[A.class,B.class]
        private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
        //订阅者--订阅事件类型列表的映射类。如A类中有两个订阅事件:Type1,Type2,则集合为: A--[Type1,Type2]
        private final Map<Object, List<Class<?>>> typesBySubscriber;
        //粘性事件与订阅者的一一映射集合
        private final Map<Class<?>, Object> stickyEvents;
    

    接下来看subscribe方法:

     private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            Class<?> eventType = subscriberMethod.eventType;
            //1.将订阅类,与订阅方法一同封装到``Subscription``类(订阅者)中
            Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
            //2.从已有map中通过EventType来获取订阅者列表
            CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
            //3.如果列表为空,则新建列表并将该订阅者添加入列表,然后将该列表作为value与该EventType映射到map中
            if (subscriptions == null) {
                subscriptions = new CopyOnWriteArrayList<>();
                subscriptionsByEventType.put(eventType, subscriptions);
            } else {
                //4.如果列表不为空,且列表已包含该订阅者,则抛出异常【一个订阅类中同一类型的方法只能有一个,即A类中不能存在两个参数都为EventType1的订阅方法】
                if (subscriptions.contains(newSubscription)) {
                    throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                            + eventType);
                }
            }
            //5.如果列表不为空且不包含当前订阅者,则轮询列表并根据该方法优先级来插入订阅者列表中
            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;
                }
            }
            //6.获取到该订阅类中所有的订阅方法EventType列表。如果列表为空,则新建。然后更新【订阅者---订阅事件类型列表】
            List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
            if (subscribedEvents == null) {
                subscribedEvents = new ArrayList<>();
                typesBySubscriber.put(subscriber, subscribedEvents);
            }
            subscribedEvents.add(eventType);
            //...
        }
    

    详细说明已经加入注释,下边用大白话总结一下:

    1. 刷新【事件类型---订阅者列表】的映射关系
    2. 刷新【订阅者---事件类型列表】的映射关系

    因为每个订阅者可以有多个订阅方法,而每个订阅方法的事件类型可能被多个订阅者定义。因此订阅者--事件类型是多对多的关系。为了方便通过订阅者找到该类中的订阅方法,或者通过订阅事件类型来找到所有订阅者,因此用两个map来建立一对多的映射关系。

    ok,register方法主要就是做了这么一件事。下面来对应着看一下unRegister方法

    EventBus.getDefault().unregister(subscriber):
        public synchronized void unregister(Object subscriber) {
            //通过订阅者找到该类中所有的订阅事件方法
            List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
            if (subscribedTypes != null) {  //如果订阅事件方法列表不为空
                //轮询每个事件类型,将该订阅者从【事件类型--List<订阅者>】列表中移除
                for (Class<?> eventType : subscribedTypes) {
                    unsubscribeByEventType(subscriber, eventType);
                }
                //将该订阅者从【订阅者--List<事件类型>】map中移除
                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++) {
                    //轮询遍历列表,并将自身从该订阅者列表中删除【动态删除List子元素做法】
                    Subscription subscription = subscriptions.get(i);
                    if (subscription.subscriber == subscriber) {
                        subscription.active = false;    //如果有post过来则不再进行反射调用的标记
                        subscriptions.remove(i);
                        i--;
                        size--;
                    }
                }
            }
        }   
    

    注释之外再用大白话总结一下:
    通过该方法一共做了两件事:

    1. 刷新【订阅者---List<事件类型>】map集合,或从map中移除该订阅者。
    2. 刷新【事件类型--List<订阅者>】map集合。或从List<订阅者>列表中删除自己。
    小结:
    1. register(订阅者):
    • 找到该订阅类中的所有方法,并以订阅者为key,List<订阅事件类型>为value存入map集合typesBySubscriber 中。
    • 再循环遍历事件类型,然后以每一个EventType作为key,List<订阅者>作为value存入map集合subscriptionsByEventType中。
    1. unRegister(订阅者)
    • 获取到该订阅者中的所有事件类型,并轮询每个事件类型获取该类型对应的订阅者列表,然后将自己从订阅者列表中移除。
    • 将该订阅者从typesBySubscriber结合中移除。

    总结完毕。上边说到在注册的时候首先会找到该订阅类中所有的订阅方法,下面来分析一下如何找到该类中所有的订阅方法。

    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    

    findSubscriberMethods方法如下:

         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;
            }
            //如果忽略索引则强制使用反射查找
            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;
            }
        }
    

    方法说明:

    1. 如果命中缓存则直接返回,否则开始查找。
    2. 如果忽略生成的索引方式,则强制使用反射查找
    3. 否则使用EventBus注解处理器生成的索引查找
    4. 将查找结果存入缓存并返回。

    先看一下反射查找法:

        private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
            FindState findState = prepareFindState();   //从对象池中取出一个,如无则新建
            findState.initForSubscriber(subscriberClass);   //初始化该查找器的起点类为订阅类
            while (findState.clazz != null) {       //clazz为一个指针,初次指向的是当前类
                findUsingReflectionInSingleClass(findState);        //在该类中找到用@Subscribe订阅的方法,并添加到findState中的一个列表中
                findState.moveToSuperclass();   //将clazz指向当前类的父类【取出java,android等系统api】
            }
            return getMethodsAndRelease(findState); //返回该查找器中查找的方法列表,然后重置查找器并添加到对象池中
        }
    

    首先说明一下FindState类是干嘛的:

            final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
            final Map<Class, Object> anyMethodByEventType = new HashMap<>();
            final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
            final StringBuilder methodKeyBuilder = new StringBuilder(128);
    
            Class<?> subscriberClass;
            Class<?> clazz;    
            boolean skipSuperClasses;
    
        //将查找器指向订阅类
         void initForSubscriber(Class<?> subscriberClass) {
                this.subscriberClass = clazz = subscriberClass;
                skipSuperClasses = false;
                subscriberInfo = null;
            }
    
        //回收查找器 
         void recycle() {
                subscriberMethods.clear();
                anyMethodByEventType.clear();
                subscriberClassByMethodKey.clear();
                methodKeyBuilder.setLength(0);
                subscriberClass = null;
                clazz = null;
                skipSuperClasses = false;
                subscriberInfo = null;
            }
         //将查找器指向父类
        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;
                    }
                }
            }
    

    姑且把该类成为查找器,专门查找某个类及其所有非系统API的父类中的所有订阅方法,并记录到自己持有的查找结果列表中。
    该类使用了对象池技术,clazz为一个指针,在查找完该类后指向自己的父类。

    因此,反射查找方法的大致思路也就理清了:通过反射手法来获取自身与所有父类中所有的订阅方法并返回。具体的查找某个类中的订阅方法实现如下:
        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;
            }
            //遍历所有方法,并挑选出所有的public,参数为一个,并且方法上有Subscribe注解的方法。
            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)) {  //将方法与参数类型建立映射
                                //获取该方法的线程,并将优先级,参数类型,方法,粘性封装到SubscriberMethod中并加入到查找器中的列表中。
                                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. 获取该类中所有(声明的)方法
    2. 循环遍历找出所有public、只有一个参数并且被@Subscribe修饰的方法
    3. 获取该方法参数类型,并将方法--参数类型建立一一映射。
    4. 如果存在非public且被@Subscribe修饰的方法则抛出异常
    5. 如果存在被@Subscribe修饰的方法并且参数不是一个则抛出异常。

    需要特别说一下的就是建立映射的逻辑,如果父类中的某个方法被子类重写了,并且子类中也在该方法上加入了注解怎么处理?答案是保留父类的方法。因为子类中的方法一般会先调用父类方法的。父类在层级关系上更加靠上。
    对应源码如下:

            boolean checkAdd(Method method, Class<?> eventType) {
                //如果之前存在eventType,则返回之前的method,否则返回null
                Object existing = anyMethodByEventType.put(eventType, method);
                if (existing == null) {     //之前不存在该eventType
                    return true;
                } else {        //已经存在了该type的方法,则进行第二重校验
                    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());   //add(Integer a)--> add>Integer
            
                String methodKey = methodKeyBuilder.toString();     //add>Integer
                Class<?> methodClass = method.getDeclaringClass();      //解决方法重载问题
                Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
                //如果之前不存在该key,或者methodClassOld是methodClass的父类,则返回true
                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;
                }
            }
    
    ok,通过反射查找订阅者及其父类中的订阅方法分析完毕,至于另外一种方法:通过注解生成器生成索引。是可选的,如果用户使用了索引,则默认会通过该方法在编译期间找到所有注册的方法。具体实现可参考:EventBusAnnotationProcessor。【此处的难点在于自定义注解处理器,如果注解处理器搞明白了就会比较简单】,因为使用索引方法是在编译器在注解处理器中动态获取所有的注册方法并输出到指定的一个类中,所以注解处理器的方法性能要比反射要高的多!

    核心代码:

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
            Messager messager = processingEnv.getMessager();
            try {
                String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
                
                if (annotations.isEmpty()) {
                    return false;
                }
    
                collectSubscribers(annotations, env, messager);
                checkForSubscribersToSkip(messager, indexPackage);
    
                if (!methodsByClass.isEmpty()) {
                    createInfoIndexFile(index);    //获取到所有的有效方法,则开始输出到类中
                } else {
                    messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
                }
                writerRoundDone = true;
            } catch (RuntimeException e) {
              
            }
            return true;
        }
    
        private void createInfoIndexFile(String index) {
            BufferedWriter writer = null;
            try {
                JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
                int period = index.lastIndexOf('.');
                String myPackage = period > 0 ? index.substring(0, period) : null;
                String clazz = index.substring(period + 1);
                writer = new BufferedWriter(sourceFile.openWriter());
                if (myPackage != null) {
                    writer.write("package " + myPackage + ";\n\n");
                }
                le("myPackage: " + myPackage + " , clazz: " + clazz);
                writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
                writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
                writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
                writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
                writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
                writer.write("import java.util.HashMap;\n");
                writer.write("import java.util.Map;\n\n");
                writer.write("/** This class is generated by EventBus, do not edit. */\n");
                writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
                writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
                writer.write("    static {\n");
                writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
                writeIndexLines(writer, myPackage);    //!!!!循环遍历,然后在静态代码块中将有效方法put进集合里边
                writer.write("    }\n\n");
                writer.write("    private static void putIndex(SubscriberInfo info) {\n");
                writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
                writer.write("    }\n\n");
                writer.write("    @Override\n");  //重写父类方法,将有效方法集合返回给外部调用者【findUsingInfo()方法中获取】
                writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
                writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
                writer.write("        if (info != null) {\n");
                writer.write("            return info;\n");
                writer.write("        } else {\n");
                writer.write("            return null;\n");
                writer.write("        }\n");
                writer.write("    }\n");
                writer.write("}\n");
            } catch (IOException e) {
                throw new RuntimeException("Could not write source for " + index, e);
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        //Silent
                    }
                }
            }
        }
        //循环遍历有效订阅方法集合,然后动态写入到指定的类中的一个集合里边
        private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
            for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
                if (classesToSkip.contains(subscriberTypeElement)) {
                    continue;
                }
    
                String subscriberClass = getClassString(subscriberTypeElement, myPackage);
                if (isVisible(myPackage, subscriberTypeElement)) {
                    writeLine(writer, 2,
                            "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
                            "true,", "new SubscriberMethodInfo[] {");
                    List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
                    writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
                    writer.write("        }));\n\n");
                } else {
                    writer.write("        // Subscriber not visible to index: " + subscriberClass + "\n");
                }
            }
        }
    

    其中,index是在gradle文件中指定的包名+类名:

            javaCompileOptions {
                annotationProcessorOptions {
                    includeCompileClasspath = true
                    arguments = [ eventBusIndex : 'com.zhu.test.EventBusTestsIndex' ]
                }
            }
    

    下面对非反射方法做下总结:

    1. 在编译期间通过注解处理器生成一个实现了SubscriberInfoIndex的类,并将扫描到的符合条件的方法都添加到一个静态集合中。
      2.*可以通过EventBus.builder().addIndex(new EventBusTestsIndex()).installDefaultEventBus();来将注解器生成的方法索引类注册到EventBus中,注册完成之后会通过构造参数传递给SubscriberMethodFinder类。之后该类会在findUsingIndex方法中直接获取注解处理器生成的类中持有的注册方法集合。

    文末总结:

    1. 通过register以及unRegister来刷新[事件类型--List<订阅者>]与[订阅者--List<事件类型>]的双向映射集合
    2. 通过反射或者注解处理器来获取所有的注册方法,处理子类重写父类中的订阅方法的情况

    关于事件的发送与分发请看EventBus源码解读下篇:事件的发送与分发

    参考:
    https://segmentfault.com/a/1190000005089229
    https://github.com/greenrobot/EventBus

    相关文章

      网友评论

          本文标题:EventBus源码解读上篇:注册与订阅

          本文链接:https://www.haomeiwen.com/subject/erbalqtx.html