EventBus系列『二』——Post与postSticky事件的发布与接收

上一篇我们针对EventBus注册注销做了源码剖析,通过流程图和对源码的剖析,应该对上述流程有了一定了解.
接下来我们将会对Post事件发布与接收postSticky事件发布与接收 展开剖析,我们将会提出几个问题,让我们带着问题去剖析这两个事件的流程。

我的EventBus系列的其他文章地址

EventBus系列『一』——注册与注销

EventBus系列『二』——Post与postSticky事件的发布与接收

EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想

问题

    1. Post事件postSticky事件的使用场景
    1. Post事件postSticky事件的区别
    1. postSticky事件实现 粘性的原理

剖析

Post事件发布

我们先上一张Post事件发布流程图:

EventBus_Post事件.jpg

我们根据流程图的执行流程,开始我们的对源码的剖析:

public void post(Object event) {
   //从ThreadLocal​中获取PostingThreadState类实例
    PostingThreadState postingState = currentPostingThreadState.get();
   //获取PostingThreadState的属性​
    List<Object> eventQueue = postingState.eventQueue;
    //将event事件添加到eventQueue队列​
    eventQueue.add(event);​
    if (!postingState.isPosting) {
        //设置PostingThreadState的isMainThread属性​
        postingState.isMainThread = isMainThread();
        //设置PostingThreadState的isPosting属性​
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            //遍历eventQueue队列​
            while (!eventQueue.isEmpty()) {
                 //[ 1 ]  ​遍历队列,逐一对队列中的元素执行发布 参数中 [ eventQueue.remove(0) ] 方法会将队列下的指定位置的元素移除,并返回被移除的元素值
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
  • 1.从ThreadLocal​中获取PostingThreadState类实例
    1. ​遍历队列,逐一对队列中的元素执行发布 参数中 eventQueue.remove(0) 方法会将队列下的指定位置的元素移除,并返回被移除的元素值
    1. 最后重置 postingState 状态属性

[ 1 ] ​遍历队列,逐一对队列中的元素执行发布,进入postSingleEvent(eventQueue.remove(0), postingState) 方法

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //获取事件的类型class​
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //判定是否是继承事件​
    if (eventInheritance) {
        //获取类型class的父类集合​
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
         //遍历类型class的父类集合​
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
             //[ 2] 为事件类型发布单个事件​,返回的结果与 subscriptionFound  进行 [ 位或运算 ] (有1则为1),
            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);
        }
        /**
           *@重点讲解:此处做一次判定
           * 1. 若正常发布的事件没有被订阅,则在此处将 当前 eventBust实例 
           * 和完成的 event事件封装成 NoSubscriberEvent类型,重新发送
           *2.若没有被订阅的事件已经是封装了NoSubscriberEvent类型的事
           *件,那么将不会再次发布了。
           */
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
             // 当找不到已发布事件的订阅者时,EventBus将此事件发布到事件总线上​ 
            post(new NoSubscriberEvent(this, event));
        }
    }
}
    1. 获取事件类型class,判定当前事件是不是继承事件
  • 2.若是继承事件,则获取类型class的父类集合,遍历父类集合, 为事件类型发布单个事件​,返回的结果与 subscriptionFound 进行 [ 位或运算 ]
  • 3.若不是继承事件,则为事件类型发布单个事件​​
    1. 判定发布的事件是否被订阅
    1. 没有被订阅, 此处做一次判定.
      正常发布的事件没有被订阅, 在此处将 当前 eventBust实例和完成的 event事件封装成 NoSubscriberEvent类型,重新发送;
      没有被订阅的事件已经是封装了NoSubscriberEvent类型的事件, 将不会再次发布了

[ 2 ] 为事件类型发布单个事件​ postSingleEventForEventType(event, postingState, eventClass) 方法

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    //通过注册类获取订阅事件集合​
    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 {
               //[ 3 ] 发布订阅事件​
                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;
}
    1. 通过注册类classsubscriptionsByEventType获取订阅信息集合
      (提示: subscriptionsByEventType集合只有在register注册的时候才会执行put,此处是post发布事件直接从中取值 ).
  • 2.做判定 subscriptionsByEventType获取的数据为是否为Null
  • 3.若为Null, 表明发布的事件还没有被订阅,也就是说事件是在执行register注册之前发布的,所以没有被订阅上,所以直接返回 False即可.
  • 4.若不为Null, 遍历订阅信息事件集合​, 发布订阅事件​

重点提示

  • 这里若是发布的Post事件没有被订阅,则该事件就会 流转,即作废.
  • 若发布的 postSticky事件没有被订阅,由于postSticky事件是将完整的事件放入内存的,当执行register注册时会从内存中获取该事件,然后包装成一个新的Post事件进行发布,由于此时register注册已执行完毕,所以postSticky事件可以正常使用.

[ 3 ] 发布订阅事件​ postToSubscription(subscription, event, postingState.isMainThread) 方法

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    //根据订阅事件的 threadMode类型 进行分发处理​
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:  // 默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高
             //[ 3.1 ] 执行调用订阅者​
            invokeSubscriber(subscription, event);
            break;
        case MAIN: // 如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
            //判断是否在主线程发送分事件
​            if (isMainThread) {  
                invokeSubscriber(subscription, event);
            } else {
                //[3.2]将订阅事件放入主线程队列,依此处理事件​
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:  // 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // 若主线程队列为空,则直接调用执行订阅者
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND: //如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。
            if (isMainThread) {
                 //[ 3.3 ] 将订阅事件放入后台队列,依次调用执行​
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:  //无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
            //​ [ 3.4 ] 将订阅事件放入线程池队列,依此调用执行订阅者
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}
    1. 根据订阅事件的 threadMode类型 进行分发处理​
  • 1.1. POSTING: 默认的线程模式,在那个线程发送事件就在对应线程处理事件,避免了线程切换,效率高
  • 1.2 MAIN: 如在主线程(UI线程)发送事件,则直接在主线程处理事件;如果在子线程发送事件,则先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件。
  • 1.3 MAIN_ORDERED: 无论在那个线程发送事件,都先将事件入队列,然后通过 Handler 切换到主线程,依次处理事件
  • 1.4 BACKGROUND: 如果在主线程发送事件,则先将事件入队列,然后通过线程池依次处理事件;如果在子线程发送事件,则直接在发送事件的线程处理事件。
  • 1.5 ASYNC: 无论在那个线程发送事件,都将事件入队列,然后通过线程池处理。
    1. 对事件进行分发后,判断决定将Event事件直接通过反射执行 或 放入相应队列中逐个执行。

postSticky粘性事件发布

通过对源码的分析我们可以发现postSticky发布过程post发布过程的区别,我们看源码:

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        //[ 1 ] 将订阅事件类型class与事件关联,放入Map集合缓存​
        stickyEvents.put(event.getClass(), event);​
    }
    //将给定的事件发布到事件总线
    post(event);
}

可以看出postSticky函数在执行post函数之前,执行了一次对完整Event事件Map集合存储,而Map集合的特性决定了在Key值相同的情况下,存储的Value值最后一次放入的准,这也正符合postSticky事件同样参数类型,以最后一次发布的事件为准 特性

Post 、postSticky事件接收

Post事件接收

发送POST事件一定要确保接收方页面执行过了 register注册,并且已存在对应的接收方法,即 被订阅完成后再执行事件发布,方可被正确接收

postSticky事件接收

postSticky事件在发布阶段进行了一次对完整Event事件的事件的缓存。
若接收方此时还未执行register注册,则 Event事件则将一直存储于内存中,直至接收方register注册 完成后,将会内存中获取完整Event事件,当做一个新Event事件发布到总线上,进而被正确获取。
我们将register注册时对postSticky事件特殊特殊处理源码粘出供大家理解:
若接收方已执行完register注册,则直接进行发布

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    ...
    ...​
     //​判断当前
    if (subscriberMethod.sticky) {
        //判断是不是继承的事件​
        if (eventInheritance) {
            //遍历粘性事件缓存集合
            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);
        }
    }
}

问题回答

[ 1 ] Post事件postSticky事件的使用场景
  • Post 适用于向上传递,不适用于向下传递,即已完成register注册操作后,再发布POST事件
  • postSticky 适用于任何场景,即register注册操作前后,都可进行postSticky事件发布
[ 2 ] Post事件postSticky事件的区别
  • Post事件必须在接收方完成register注册操作后发布才能正常被接收到,否则无法被接收;而postSticky事件则没有这样的限制要求。
  • Post发布的事件若没有被订阅的事件会将发生流转,即 作废,无效;而postSticky发布的事件未被订阅,则将存储于内存中,当订阅者完成register注册后从内存中取出,当做一个新事件进行发布。
[ 3 ] postSticky事件实现 粘性的原理
  • postSticky的原理是:postSticky在发布时将完整的事件存储于全局Map集合 stickyEvents中,而当接收方进行register注册是,会对粘性事件做特殊处理,从stickyEvents Map集合中取出相应的事件,交由postToSubscription函数,进行重新发布.

This ALL! Thanks EveryBody!

请关注下篇

EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想

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

推荐阅读更多精彩内容