专栏首页每天学点Android知识EventBus源码分析之发布流程

EventBus源码分析之发布流程

继上篇文章EventBus源码分析之订阅流程之后,继续介绍EventBus的发布,事件发送完,EventBus如何做到调用之前注册的方法。

发布者发布事件,事件如何到订阅方法的

其实看完上面的代码,应该有个大体思路了,东西都保存在了EventBus中,发布者发完事件,EventBus根据事件去找到所有订阅方法,然后反射调用就OK了,下面我们将实践看一下,是不是这么一个步骤。

EventBus.post()

一切从发布者的post()方法说起,源码如下:

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }

    public void post(Object event) {
        //每个线程创建一个PostingThreadState,将事件加入队列
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);


        if (!postingState.isPosting) {
            //是否在主线程发步的
            postingState.isMainThread = isMainThread();
            //置标志位
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                //将队列中所有事件逐个发布出去
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

上面涉及ThreadLocal,不了解的朋友可以参考ThreadLocal源码分析。PostThreadState会为每个线程创建一份,然后共用。同一个线程中发布的事件都会存到它的List中。 postSingleEvent()就是发送单个事件的方法,

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //case:事件继承,默认false,需要找出该事件的继承结构
        if (eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        //如果没有找到订阅方法消费
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

从上面可以看到,核心代码是调用了postSingleEventForEventType()方法,该方法的代码如下:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        //加锁,因为注册线程会向该Map中插入数据
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        //如果有该事件类型的订阅元组
        if (subscriptions != null && !subscriptions.isEmpty()) {
            //遍历元组,由于存放的时候是按优先级的,所以这里也按优先级进行消费
            for (Subscription subscription : subscriptions) {
                //置状态
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    //发布到单个订阅元组
                    postToSubscription(subscription, event, postingState.isMainThread);
                    //是否被中途取消了
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                //如果中断了,那么退出
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

可以看到最终调用了postToSubscription()方法将事件发布给订阅者,该方法在上面已经碰到了,最终是通过反射进行调用的;这样依次将每一个事件完成了发布。

取消事件分发

上面涉及到一个参数,PostingThreadState的canceled参数,该参数会在取消事件时标志。取消事件分发是在事件消费方法中调用cancelEventDelivery()方法,该方法的限制场景是ThreadMode是POSTING的情景下,下面会说明原因。该方法的代码如下:

public void cancelEventDelivery(Object event) {
        //获取当前线程的PostingThreadState
        PostingThreadState postingState = currentPostingThreadState.get();
        //情况判断,当前不在分发事件,抛出异常
        if (!postingState.isPosting) {
            throw new EventBusException(
                    "This method may only be called from inside event handling methods on the posting thread");
        } else if (event == null) {
            throw new EventBusException("Event may not be null");
        } else if (postingState.event != event) {
            throw new EventBusException("Only the currently handled event may be aborted");
        } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
            throw new EventBusException(" event handlers may only abort the incoming event");
        }

        //设置标志位,后续事件将在分发中终止
        postingState.canceled = true;
    }

上面说过,每个线程有一个PostingThreadState,而分发方法和订阅方法都会获取PostingThreadState进行标志位的设置来达到消息通信的,这要求必须得是同一个对象,也就是说发布和订阅是在同一个线程中,而ThreadMode为POSTING的情况下,发布和事件消费是在同一个线程中,这儿也能想象该类为啥叫PostingThreadState了。

总结

经过上面的源码分析,可以理解事件中心是如何保存订阅者的,订阅者为啥只需调用register()方法,其他就可以什么都不管了,因此事件中心会利用反射找出@Subscribe注解了的方法,然后保存起来;发布者为啥只要post()出事件,剩下的就不要管了,因为事件中心会去寻找出之前保存的订阅者以及订阅方法,然后通过反射进行调用。

本文分享自微信公众号 - 每天学点Android知识(android_every_day),作者:星风coder

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • EventBus源码分析之线程分发

    EventBus的线程分发中介绍了EventBus中发布和订阅方法设置了ThreadMode之间的关系,最终表格如下:

    用户1108631
  • ArrayMap数据结构分析

    ArrayMap是Android上特有的一个性能比较高的Map,和HashMap一样,也实现了Map接口。

    用户1108631
  • 玩Android客户端(1)——搭建主页面

    最近在学Flutter,想着画点时间做个app,就做玩Android,可以利用现有的APi进行操作。 第一步:搭建主页面,如下:

    用户1108631
  • 吾有一术,名曰炼丹。北大博士生用文言开发深度学习网络,还有Pytorch脚本

    最近文言编程语言 / wenyan-lang火了——GitHub项目已经超过12.7K Stars。

    新智元
  • 谷歌Joe Brennan:美国的软件专利

    11月22-23日,由腾讯互联网与社会研究院与北京大学法学院、斯坦福大学法学院、牛津大学法学院联合举办的第三届“2014北大-斯坦福-牛津:互联网法律与公共...

    腾讯研究院
  • ERP的管理思想是什么?

      全面质量管理是面向客户的质量管理,质量不是一次性的“验收”,而是持续不断改进。今天的质量验收标准会随着客户期望值的提高而过时。全面质量管理的观念是:下道工序...

    明象ERP
  • 【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第二节)

    剽悍一小兔
  • Mac 开发之 做一个JSON转模型属性的小公举

    好啦,到这里就简单完成了这个小工具,继续学习的,可以给这个工具添加一个状态栏按钮(可以参考我前面的文章),也许会用起来更方便哦..

    代码行者
  • 宜信开源|手把手教你安装第一个LAIN应用

    三者都需要从 GitHub 获取已经发布的 LAIN 版本源代码: https://github.com/laincloud/lain/releases

    宜信技术学院
  • 将《黑镜》中的科技带入现实,IBM申请视频审查系统专利

    VRPinea

扫码关注云+社区

领取腾讯云代金券