美文网首页源码解析
EventBus源码解析(二)——注册

EventBus源码解析(二)——注册

作者: 海晨忆 | 来源:发表于2018-03-08 13:29 被阅读3次

    个人博客: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

    相关文章

      网友评论

        本文标题:EventBus源码解析(二)——注册

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