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源码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容

  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,970评论 4 42
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • EventBus源码分析(二) 在之前的一篇文章EventBus源码分析(一)分析了EventBus关于注册注销以...
    蕉下孤客阅读 1,645评论 0 10
  • 原文链接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy阅读 537评论 1 5
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,464评论 0 50