EventBus源码分析(三): 注册和解绑的源码分析

前言

为了让文章的篇幅保持一个短小精悍的程度,所以决定把EventBus的分析拆分成:注册解绑发送Event的处理过程两部分。

今天来看看注册解绑是怎么操作的。

正文

EventBus首先需要注册,才能得到Event的响应,这里省略了某些类的源码,直接看过程:

/**
     * 这里来注册广播,并且提示接收Event的方法必须被Subscribe注解
     * */
    public void register(Object subscriber) {
        // 先得到类名
        Class<?> subscriberClass = subscriber.getClass();
        // 找到这类被注解的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 遍历查找到的被注解的方法
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                // 把注册的类和被注解的方法保存起来
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

在注册的方法中,先根据注册对象的Class,找到内部被注解的方法,然后对每个被注解的方法再一次进行处理。我们先看看是怎么得到Class中被注解的方法的,SubscriberMethodFinder是用来查找某个Class中被注解方法的辅助类,看一下findSubscriberMethods(Class)方法:

/**
     * 找到某一个类中被注解的方法
     * */
    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;
        }
    }

首先先从缓存中获取,这样之前已经查找过的内容就可以直接得到了,提升了某一个Class反复注册的速度,例如我们经常在onResume和onPause注册和解绑,这样就不用担心效率的问题了。

然后是判断我们在EventBusBuilder中的设置,是否要忽略我们之前生成的文件,建议不要忽略,我们知道编译生成的Java文件,已经把Class和被注解方法都保存到一个Map集合里了,就省去了查询的时间。

不过我们还要去看看他是怎么得到Class被注解的方法的,打开findUsingReflection()方法:

/**
     * 通过反射的到类的被注解的方法
     * */
    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        // 得到一个查找类对象
        FindState findState = prepareFindState();
        // 设置要查找的类
        findState.initForSubscriber(subscriberClass);
        // 开始循环查找
        while (findState.clazz != null) {
            // 通过反射查找类中被注解的方法
            findUsingReflectionInSingleClass(findState);
            // 接着查找父类
            findState.moveToSuperclass();
        }
        // 得到刚才循环查找的结果
        return getMethodsAndRelease(findState);
    }

/**
     * 通过反射,查找使用一个类中使用注解的方法
     * */
    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;
        }
        // 遍历这些方法
        for (Method method : methods) {
            // 得到这个方法的修饰符
            int modifiers = method.getModifiers();
            // 如果是public 并且忽略几种类的情况(抽象类,静态类等)
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 得到方法参数类型数组
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 参数的个数是1
                if (parameterTypes.length == 1) {
                    // 得到这个方法的Subscribe注解
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    // 如果这个方法被Subscribe注解了
                    if (subscribeAnnotation != null) {
                        // 得到参数的class类型
                        Class<?> eventType = parameterTypes[0];
                        // 检查是否添加成功
                        if (findState.checkAdd(method, eventType)) {
                            // 获取注解的ThreadMode的值
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            // 还记得注解库中的那段乱遭的代码吗,他们的作用是一样的,把类和对应的注解的方法,保存起来
                            // 请注意eventType,这个是把参数的类型作为eventType
                            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、从当前类开始,一直遍历到最顶端的父类,到Java类和android类为止。
2、遍历当前类中所有的方法,判断方法是否有Subscribe注解,并且符合要求,要求和在编译库中的是一样的,这里就不再重复说明了。

刚才是如果忽略了编译生成的文件,获取Class中被注解的方法,下面再看看正常情况如果得到被注解的方法的,打开findUsingInfo()方法:

/**
     * 从编译生成的文件中查找被注解的方法
     * */
    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);
    }

/**
     * 获取被注解的方法的信息
     * */
    private SubscriberInfo getSubscriberInfo(FindState findState) {
        // 如果查找类本身有注解相关的信息,因为是异步的,有可能这个类还是被回收
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            // 判断现在要查找的class 和 之前查找的class是否相同,如果相同直接返回结果
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        // 这里可以通过Builder来设置subscriberInfoIndexes,添加我们生成的类
        // 然后就可以直接从生产的类中直接查找信息
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

如果是正常的使用,首先会从查找类中查询结果,例如之前查询过这个Class并且结果还没有被回收,然后再从生成的编译文件中获取,如果还是没有,只能通过反射再去查询一遍,确保没有被漏掉的被注解方法。

getMethodsAndRelease方法就不用说明了,他就是获取FindState中查询出来的List而已。

到此为止,获取注册对象中被注解的方法就到此结束了,我们又需要回到注册方法,看看对于每一个被注解的方法,EventBus都做了哪些处理,打开subscribe()方法:

/**
     * 把注册的类和对应的被注解的方法保存起来
     * */
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 得到对应的Event,也就是参数的类型
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 增加了一个判空,添加在到subscriptionsByEventType中
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            // 增加不允许重复的判断
            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;
            }
        }

        // 这里把注册的类保存了起来,最终还要从typesBySubscriber解绑
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        // 把绑定的EventType放入list中
        subscribedEvents.add(eventType);

        // 判断是否是StickyEvent,带有上一次缓存的广播
        if (subscriberMethod.sticky) {
            // 如果StickyEvent可以被继承
            if (eventInheritance) {
                // 找到StickyEvent的Set集合
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                // 遍历集合中的元素
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    // 得到注册StickyEvent的Class
                    Class<?> candidateEventType = entry.getKey();
                    // 判断是否是集成关系
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        // 得到Class对应的Event
                        Object stickyEvent = entry.getValue();
                        // 检查是否是要给当前注册的类,返回StickyEvent
                        // 也就是注册了之后,仍然可以得到之前缓存的StickyEvent
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

代码有些长,其实整体的操作可以分为三个部分:

1、根据Event的类型,保存对应的Class和被注解的方法的列表,并且根据优先级排序,这样优先级高的,就能先得到Event。
2、把注册的对象的Class和被注解的方法保存起来,这样方便解绑时的操作。
3、终于看到了StickyEvent了,如果是StickyEvent,从StickyEvent中的缓存中找到对应的缓存,响应给正在注册的对象。

这样注册的流程就结束了,中间我忽略了一些方法的介绍,但是我都加了注释,而且内部的实现是很简单,要不这篇博客真的是越写越长了。

最后看一下解绑:

    /**
     * 解绑注册
     * */
    public synchronized void unregister(Object subscriber) {
        // 获取对象的被注解的方法
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 先循环移除绑定的Event
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 解绑
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

/**
     * 移除对象绑定的EventType
     * */
    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++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

其实是和注册相反的操作,先把被注解的方法一个个的移除,最后移除这个Class,但是中间少了很多的判断等等的操作,就变成了短短几行。

总结

我们刚刚分析EventBus整体的流程,有了注释代码的一步步操作就清晰多了,但是为了减少篇幅,有些方法没有给大家介绍,但是不要着急,这些方法中我都添加了注释,保证大家能够看明白,包括之后要分析到的Post Event的过程的注释,都已经上传到了github中,大家可以去看更多的分析注释。

添加了注释的EventBus源码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值