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与postSticky事件的发布与接收_第1张图片
EventBus_Post事件.jpg

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

public void post(Object event) {
   //从ThreadLocal​中获取PostingThreadState类实例
    PostingThreadState postingState = currentPostingThreadState.get();
   //获取PostingThreadState的属性​
    List 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> 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 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. 通过注册类class从subscriptionsByEventType获取订阅信息集合
      (提示: 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, 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);
        }
    }
}

问题回答

[ 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』队列的实现思想

你可能感兴趣的:(EventBus系列『二』——Post与postSticky事件的发布与接收)