Greenrobot-EventBus源码学习(五)

时间:2022-04-27
本文章向大家介绍Greenrobot-EventBus源码学习(五),主要内容包括EventBus 深入学习五之注册、3. 小结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

EventBus 深入学习五之注册

订阅者的注册 + 消息推送

1. 注册

先贴出注册代码, 可以可到和 Guava 相比没什么大的区别, 主要的点在内部实现上,一个是如何获取注册信息;一个是如何保存注册关系

 /**
     * 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.
     */
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        // 查询注册方法的核心
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 维护订阅关系的核心代码
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

获取订阅信息 (构建 SubscriberMethod

注解方式获取

从下面的代码可以看出,获取注解方法的流程是:

  • 获取类的所有方法
  • 只有一个参数的方法,判断是否有 @subscribe注解
  • 其他都过滤(视情况是否抛异常出来)
 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();
            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)) {
                            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");
            }
        }
    }

对比Guava的获取注解方法, 实现的主要区别是Guava多了一个获取超类的过程

- Guava获取所有的超类, 根据每个类的 `getDeclaredMethods` 获取所有的方法,然后判断是否有注解
- Greenrobot 则是直接调用类的  `getDeclaredMethods` 获取所有方法, 然后判断是否有注解

上面两个有什么区别 ?

class.getDeclaredMethods 可以获取类所有申明的方法,也就是说 private, protected, 默认,public四个作用域的都可以获取到,换句话说,Guava的订阅者方法可以是私有的!!!,即便父类的私有方法也是可以的, static也无所谓

Greenrobot 中限制了方法的作用域共有的非静态方法,有且只有一个参数,而且只是对当前类而言

非注解方式

支持非注解方式进行注册,主要借助SubscriberInfoIndex 来指定注册方法 。我们可以倒推一下这个设计思路:

  • 注册,首先是要确定将类的哪些方法注册到 EventBus
  • 排除掉注解方式;还有一种常见的就是我们定义一种方式,可以将我们需要注册的方法直接返回
  • 定义一个接口,用于返回注册类的所有订阅信息,然后把这个接口也丢给 EventBus

下面贴一个非注解方式的测试用例,方便理解, SubscriberInfoIndex就是我们定义用于返回所有的订阅关系的接口,通过调用 EventBus.addIndex(非线程安全)方法告知eventbus对象

public class EventBusIndexTest {
    private String value;

    @Test
    /** Ensures the index is actually used and no reflection fall-back kicks in. */
    public void testManualIndexWithoutAnnotation() {
        SubscriberInfoIndex index = new SubscriberInfoIndex() {

            @Override
            public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
                Assert.assertEquals(EventBusIndexTest.class, subscriberClass);
                SubscriberMethodInfo[] methodInfos = {
                        new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class)
                };
                return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos);
            }
        };

        EventBus eventBus = EventBus.builder().addIndex(index).build();
        eventBus.register(this);
        eventBus.post("Yepp");
        eventBus.unregister(this);
        Assert.assertEquals("Yepp", value);
    }

    public void someMethodWithoutAnnotation(String value) {
        this.value = value;
    }
}

下面则开始进入正式的代码纬度分析

    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();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

维护注册关系

通过上面可以将订阅者所有注册方法找出来,找出来之后当然是要存起来,也就是这一小节的内容,如何存,存了之后如何用 private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; 这个东西保存的就是 Event -> 订阅方法的映射关系, 相当于Guava的 SubscriberRegistry

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //////////////////////////
        ////// 开始将订阅信息塞入 subscriptionsByEventType 时间-注册关系映射表中
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        
        // 不存再, 则塞空; 已存在, 则抛异常
        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);
            }
        }
        

        // 根据排序,从后往前判断,应该插入什么位置,也就是说相同的优先级,越早注册的,越先获取消息
        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);
        }
        subscribedEvents.add(eventType);


        // 这里就是对粘性事件的处理
        if (subscriberMethod.sticky) {
            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 {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

##2. 取消注册

这个没什么好说的,就是上面的逆过程

3. 小结

上面分析了注册的流程,基本上思路和 Guava 的没什么大的区别,不同的是,做了更多的东西,实现了更多的功能,其中我们可以参考的几个设计思路

  1. 经典单例模式的 defaultInstance, 这个在jdk里面用得非常多,比如 Boolean.True, Collections.EMPTY_LIST,会给一些默认的实例,不用每次都去创建,上面的实现其实有不同的,实际对比之后比较明显可以感知
  2. 注解方式注册
    • 如何获取某注解的方法,这个使用和guava的有些区别,特别是对超类以及作用域的情况处理
  3. 非注解方式注册
    • 典型的借用辅助类(SubscriberInfoIndex)来完成预期目标

此外实现的细节上也可以看看,参考优秀的写法才能提高自己的书写质量,当然每个人的习惯都不一样,就比如对EventBus类中的某些用法,本人持保留意见

大量使用内部类,且属性基本上都直接访问,不通过Getter/Setter方法 (讲道理,对这种方式还是不太习惯)
EventBus中大量的配置属性,个人倾向使用Option配置类来做,会使类结构更加清晰