博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Greenrobot-EventBus源码学习(五)
阅读量:5987 次
发布时间:2019-06-20

本文共 10061 字,大约阅读时间需要 33 分钟。

hot3.png

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.     * 

* 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
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
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
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
> 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
). Set
, Object>> entries = stickyEvents.entrySet(); for (Map.Entry
, 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配置类来做,会使类结构更加清晰

转载于:https://my.oschina.net/u/566591/blog/884682

你可能感兴趣的文章
Linux 下模拟Http 的get or post请求(curl和wget两种方法)
查看>>
Windows去除快捷箭头
查看>>
关于分页的解决方案收集
查看>>
angularjs指令参数transclude
查看>>
GPT(保护分区)解决办法
查看>>
图像美颜篇(磨皮、锐化、美白)
查看>>
Observer模式
查看>>
写的比较规范的网站
查看>>
使用eclipse生成文档(javadoc)主要有三种方法:
查看>>
ajax提交json数据,后台解析问题
查看>>
【转】iOS开发里的Bundle是个啥玩意?!
查看>>
2016第43周四
查看>>
Qt Creator快捷键
查看>>
解读Raft(四 成员变更)
查看>>
mysql case when 判断null
查看>>
Convert enumeraltor to Dictionary object
查看>>
ios中封装网络和tableview的综合运用
查看>>
如何做好微信营销?
查看>>
Ubuntu下将python从2.7升级到3.5
查看>>
Fastboot线刷“复活”之刷机心得(三)——错误处理
查看>>