从 EventBus 三个方法入手看源码(二)

113 阅读8分钟

以前发在 CSDN 上, blog.csdn.net/u014443348/…

掘金氛围感觉更好一点,打算慢慢转过来。



源码基于 EventBus 3.0.0

EventBus

上一篇我们从三个方法入手分析了 EventBus从 EventBus 三个方法入手看源码(一)

接着上一篇文章,我们大概浏览下 EventBus 的结构,发现代码也并不多,那我们就接着继续看看其他方法,主要从 public 的方法下手。

大

sticky()

大概晃了一下,发现其实上一节还有一个疑问,就是  @Subscribe 中的 sticky() 属性有什么作用 ?当时我们是直接跳过了。

现在从这个类的结构里面,能看到 post() 方法还有个类似的 postSticky()方法,那我们先去瞅瞅这个方法。


public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

private final Map<Class<?>, Object> stickyEvents;

EventBus(EventBusBuilder builder) {
    ……
    stickyEvents = new ConcurrentHashMap<>();
}

能看到 stickyEvents 是一个 Map,并且有点眼熟。

对,在上一篇文章中我们刚好在 subscribe 略过了。


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);
        }
    }
}

之前分析过 eventInheritance 为 true ,*stickyEvents.entrySet() * 就是 Class<消息事件>  的集合。

通过 eventType.isAssignableFrom(candidateEventType) 判断 stickySet 中的键是否与该次 subsribe 中绑定的 消息事件 是否为同一个类,如果是的话,调用 checkPostStickyEventToSubscription()


private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
        // --> Strange corner case, which we don't take care of here.
        postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
    }
}

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
          ……
        case MAIN:
        ……
        case BACKGROUND:
          ……
        case ASYNC:
           ……
    }
}

是不是突然线索都联系上了:如果是粘性的事件,那么会先放在一个 Set 当中,如果调用 subscribe 时,将会把这个事件消费掉。

那什么情况需要出现这种用法 ? 我们对比 post() ,按照官方的提示,需要先订阅,再发消息。

那如果没订阅发消息,就没有对应的消息处理,如果我需要在订阅之前发的消息,在订阅时仍然可以处理要怎么做 ? 不就刚好符合这个处理逻辑嘛,真是机智。

需要注意:stickyEvents 是一个 Map 结构,那么一般 Map.put 的实现,会返回一个值。 如果返回 null 一般认为是插入成功,如果返回是非空值,则是取出的之前的数据。

那么就是说,我们的 stickyEvents 只能每个 消息事件 类,保存最近的一个消息。现在再回头看  @Subscribe 中 sticky() 的注释。

the most recent 是重点,刚好也应证了这个逻辑。


/**
 * If true, delivers the most recent sticky event (posted with
 * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
 */
boolean sticky() default false;

好了,现在让我们先用代码来验证下。Activity 中加入如下代码。

先注册再发消息

@Override
protected void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
    Log.d("EventBus", "register:");
}

@Override
protected void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
    Log.d("EventBus", "un register:");
}

@Override
protected void onResume() {
    super.onResume();
    test();
}

 private void test() {
    EventData eventData = new EventData();
    eventData.name = "hello";
    eventData.number = 123;
    Log.d("EventBus", "post event:");
    EventBus.getDefault().post(eventData);
}

//默认是 POSTING 模式,也可以不写括号内容
@Subscribe(threadMode = ThreadMode.POSTING)
public void onReceiveEvent(EventData eventData){
    Log.d("EventBus", "eventData : onReceiveEvent :" + eventData);
}

public static class EventData {
    public String name;
    public int number;

    @Override
    public String toString() {
        return "EventData{" +
                "name='" + name + ''' +
                ", number=" + number +
                '}';
    }
}

测试结果:
com.example.xxx D/EventBus: register:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello’, number=123}

按照正常流程使用时没有问题的,现在把发消息移到 onStart() ,注册放到 onResume() 中,来看下结果。

先发消息再注册(sticky = false)

@Override
protected void onStart() {
    super.onStart();
    test();
    Log.d("EventBus", "register:");
}

@Override
protected void onResume() {
    super.onResume();
    EventBus.getDefault().register(this);
}

测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: register:

如之前代码分析一样,EventBus 也提示,没有对应的订阅,那我们现在把 test() 中 post() 方法改成 postSticky() 看看是不是也如之前分析一样。

先发消息再注册(sticky = true)

private void test() {
    EventData eventData = new EventData();
    eventData.name = "hello";
    eventData.number = 123;
    Log.d("EventBus", "post event:");
    EventBus.getDefault().postSticky(eventData);
}

测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: register:

咦 …… 怎么和刚才一样,感觉没生效,不对啊,之前我们分析这么合理。

回头检查检查,发现我们是使用了 postSticky ,添加进之前说的数组中了,但是注册的时候没有对应处理,回头想想,应该有个 if (subscriberMethod.sticky) 的判断,这个值是在  @Subscribe 注解中读到的,现在我们来改改。


@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void onReceiveEvent(EventData eventData){
    Log.d("EventBus", "eventData : onReceiveEvent :" + eventData);
}

测试结果:
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello’, number=123}
com.example.xxx D/EventBus: register:

现在对了,果然分析没有错,还有一个要验证的问题,就是如果注册之前发了两个粘性事件,是不是只会收到最近那个 ? 现在来改改发送消息。

发送两次消息(sticky = true)

private void test() {
    EventData eventData = new EventData();
    eventData.name = "hello old";
    eventData.number = 123;
    Log.d("EventBus", "post event:");
    EventBus.getDefault().postSticky(eventData);
    EventData eventData2 = new EventData();
    eventData2.name = "hello new";
    eventData2.number = 321;
    EventBus.getDefault().postSticky(eventData2);
}

测试结果
com.example.xxx D/EventBus: post event:
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: No subscribers registered for event class com.example.xxx.test.EventData
com.example.xxx D/EventBus: No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
com.example.xxx D/EventBus: eventData : onReceiveEvent :EventData{name=‘hello new’, number=321}
com.example.xxx D/EventBus: register:

结果如我们所料,只收到了一个最新的消息,EventBus 也因为发了两次事件,所以提示了两轮。


接着然后我们来看看 cancelEventDelivery() 这个方法。

cancelEventDelivery()


public void cancelEventDelivery(Object event) {
    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;
}

从方法的名字上来看,意思是取消对于某个事件的处理,但是在上一篇文章中关于 post() 中有一段代码。


public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    ……
    if (!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        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;
        }
    }
}

从这段代码中能看到 isPosting 的作用,貌似是在从队列中发出事件时,作为一个正在发出标记,如果上次没发送完,就有新的 post() ,只是先放进队列而已。

而每次发送完毕后,会将 isPosting 至为 false ,等待下次调用时再进行判断。那这个 cancelEventDelivery() 使用第一步就是判断 isPosting 才可以走下一步,感觉不太妥当,因为可能有放入队列但是没有执行 postSingleEvent() 的消息。个人认为是除了把 cancel 标志位改过之后,还需要把 eventQueue 中的消息对比之后删除比较合理。

那接下来看看 cancel 的处理逻辑。


public void post(Object event) {
    ……
    postSingleEvent(eventQueue.remove(0), postingState);
}

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    ……
    subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);           
}

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    ……
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            ……
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            //如果 canel 后 aborted = true,就结束执行循环。
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

cancelEventDelivery() 方法也分析完了,还有一个方法也可以说说。

clearCaches();


/** For unit test primarily. */
public static void clearCaches() {
    SubscriberMethodFinder.clearCaches();
    eventTypesCache.clear();
}

static void clearCaches() {
    METHOD_CACHE.clear();
}

从名字上来看,这个方法是清除缓存的。而且 METHOD_CACHE 这个名字看起来也好面熟,仿佛是在找带有  @Subscribe 方法时见过。

那我们从 register() 中开始搜索。


List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

       ……
       
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

记得上篇文章,按着我们第一次走的逻辑,METHOD_CACHE.get(subscriberClass) == null ,那既然有非 null 的时候,可以大胆猜测下。

是不是与 register() 与 unregister() 有关,如果是第一次注册,就保存下来,如果取消了之后,不清除缓存,下次就直接获取。


/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

果然对应方法都没有对 MOETHOD_CACHE 进行清理,那么也验证了猜想,如果第一次注册,则正常获取,如果是取消注册后,第二次注册,那么就从缓存中获取。

eventTypesCache 这个值是在 lookupAllEventTypes() 中使用的,保存类与他的父类,上篇文章中简单说了下,按照当前的使用习惯,这个变量并没有多少发挥的价值,这里也是简单略过吧。


/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
    synchronized (eventTypesCache) {
        List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
        if (eventTypes == null) {
            eventTypes = new ArrayList<>();
            Class<?> clazz = eventClass;
            while (clazz != null) {
                eventTypes.add(clazz);
                addInterfaces(eventTypes, clazz.getInterfaces());
                clazz = clazz.getSuperclass();
            }
            eventTypesCache.put(eventClass, eventTypes);
        }
        return eventTypes;
    }
}

其他方法就没什么分析的空间了。

此分析纯属个人见解,如果有不对之处或者欠妥地方,欢迎指出一起讨论。