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

个人博客: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() 方法

扫描二维码关注公众号,回复: 942427 查看本文章
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

猜你喜欢

转载自blog.csdn.net/qq_27634797/article/details/79474336