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

版权声明:本文章原创于 RamboPan ,未经允许,请勿转载。



源码基于 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);
            }
        }
    }
    

之前分析过 eventInheritancetruestickyEvents.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 只能每个 消息事件 类,保存最近的一个消息。现在再回头看 @Subscribesticky() 的注释。

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章