EventBus原理与源码解析

作者: Dotry | 来源:发表于2018-10-25 20:48 被阅读41次

    1、概述

    EventBus是针对Android优化的发布-订阅事件总线,简化了Android组件间的通信。EventBus以其简单易懂、优雅、开销小等优点而备受欢迎。关于其如何使用网上有很多教程,也可以从其官网中了解到基本用法。其中整体事件流程的走向基本上可以用官网的一张图来说明,如下:

    EventBus-Publish-Subscribe.png
    publisher通过post来传递事件到事件中心Bus,Bus再将事件分发到Subscriber。其本质是一个观察者模型,最核心部分也就是Bus如何接收消息和分发消息。

    2、基本概念:

    在讲解源码之前,先说一下EventBus需要关注的点 - EventBus支持的四种线程模式(ThreadMode)。如我们常常采用以下方式使用

      @Subscribe(threadMode = ThreadMode.POSTING)
        public void getEventBus(Integer num) {
            if (num != null) {
                Toast.makeText(this, "num" + num, Toast.LENGTH_SHORT).show();
            }
        }
    

    其中@Subscribe(threadMode = ThreadMode.POSTING)也可以写成@Subscribe
    1. POSTING(默认):事件在哪个线程发布,就在哪个线程消费,因此要特别注意不要在UI线程进行耗时的操作,否则会ANR。
    2. MAIN:事件的消费会在UI线程。因此,不宜进行耗时操作,以免引起ANR。
    3. BACKGROUND:如果事件在UI线程产生,那么事件的消费会在单独的子线程中进行。否则,在同一个线程中消费。
    4. ASYNC:不管是否在UI线程产生事件,都会在单独的子线程中消费事件。

    除此之外EventBus还支持粘性事件,即发送一个未注册的粘性事件,注册者会在完成注册之后收到这个粘性事件。

    3、源码解析

    正如官网图和上文所说,EventBus最核心的部分就是其消息注册和分发中心,如何将消息注册者和消息接收这绑定起来达到准确的分发,这个当是难点。接下来我们通过起源码来逐一分析和解读。从官网中下载EventBus的源码,可以知道其源码并不是很多,整体结构基本如下:

    eventbus_sources.png
    源码结构相对来说是非常清晰了,大佬就是大佬。
    我们开始使用EventBus的时候都会采用如下方式注册使用
    EventBus.getDefault().register(this);
    

    通过查看getDefault方法

    /** Convenience singleton for apps using a process-wide EventBus instance. */
        public static EventBus getDefault() {
            EventBus instance = defaultInstance;
            if (instance == null) {
                synchronized (EventBus.class) {
                    instance = EventBus.defaultInstance;
                    if (instance == null) {
                        instance = EventBus.defaultInstance = new EventBus();
                    }
                }
            }
            return instance;
        }
    

    发现是一个“双重校验锁”的单例模式。
    查看EventBus

    EventBus(EventBusBuilder builder) {
            logger = builder.getLogger();
            subscriptionsByEventType = new HashMap<>();
            typesBySubscriber = new HashMap<>();
            stickyEvents = new ConcurrentHashMap<>();
            mainThreadSupport = builder.getMainThreadSupport();
            mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
            backgroundPoster = new BackgroundPoster(this);
            asyncPoster = new AsyncPoster(this);
            indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
            subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                    builder.strictMethodVerification, builder.ignoreGeneratedIndex);
            logSubscriberExceptions = builder.logSubscriberExceptions;
            logNoSubscriberMessages = builder.logNoSubscriberMessages;
            sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
            sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
            throwSubscriberException = builder.throwSubscriberException;
            eventInheritance = builder.eventInheritance;
            executorService = builder.executorService;
        }
    

    构造者,里面会初始化一些基础变量。

    3.1注册

     public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();//1
            List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//2
            synchronized (this) {
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    subscribe(subscriber, subscriberMethod);//3
                }
            }
        }
    
    1. 获取注册的者上下文,比如说Activity
    2. 通过注册者的上下文查找注册的事件方法
    3. 将注册者和注册的事件绑定起来,即完成了注册,可以查看subscribe方法
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            Class<?> eventType = subscriberMethod.eventType;
            Subscription newSubscription = new Subscription(subscriber, subscriberMethod);//1
            CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//2
            if (subscriptions == null) {
                subscriptions = new CopyOnWriteArrayList<>();
                subscriptionsByEventType.put(eventType, subscriptions);//3
            } else {
                if (subscriptions.contains(newSubscription)) {
                    throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                            + eventType);
                }
            }
    
            int size = subscriptions.size();
            for (int i = 0; i <= size; i++) {
                if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                    subscriptions.add(i, newSubscription);
                    break;
                }
            }
    
            List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
            if (subscribedEvents == null) {
                subscribedEvents = new ArrayList<>();
                typesBySubscriber.put(subscriber, subscribedEvents);
            }
            subscribedEvents.add(eventType);
    
            if (subscriberMethod.sticky) {//4
                if (eventInheritance) {
                    // Existing sticky events of all subclasses of eventType have to be considered.
                    // Note: Iterating over all events may be inefficient with lots of sticky events,
                    // thus data structure should be changed to allow a more efficient lookup
                    // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                    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. 将注册者和事件消费方法封装起来,这里才是真正的绑定。
    2. 就像上述注册者和事件消费方法是1:N的关系。一个Event与注册者之间也是1:N的关系。因为一个Event可能会被不同的Activity注册。也就是说Event、注册者、事件消费方法的关系是:1:N:M(其中M、N均大于等于1)。
    3. 注册者(比如MainActivity.this)与事件消费方法(SubscriberMethod)的关系,我们封装到了Subscription(s)中了。而Event和Subscription(s)的关系,我们通过HashMap保存,key为event.class,value即Subscription(s)。
    4. 黏性事件的处理。

    3.2发布与消费

    public void post(Object event) {
            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);//1
                    }
                } finally {
                    postingState.isPosting = false;
                    postingState.isMainThread = false;
                }
            }
        }
    
    1. 通过前面的状态判断,走到这一步真正消费事件。继续往下走该方法,我们可以发现其最终是调用方法
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
            switch (subscription.subscriberMethod.threadMode) {
                case POSTING:
                    invokeSubscriber(subscription, event);
                    break;
                case MAIN:
                    if (isMainThread) {
                        invokeSubscriber(subscription, event);
                    } else {
                        mainThreadPoster.enqueue(subscription, event);
                    }
                    break;
                case MAIN_ORDERED:
                    if (mainThreadPoster != null) {
                        mainThreadPoster.enqueue(subscription, event);
                    } else {
                        // temporary: technically not correct as poster not decoupled from subscriber
                        invokeSubscriber(subscription, event);
                    }
                    break;
                case BACKGROUND:
                    if (isMainThread) {
                        backgroundPoster.enqueue(subscription, event);
                    } else {
                        invokeSubscriber(subscription, event);
                    }
                    break;
                case ASYNC:
                    asyncPoster.enqueue(subscription, event);
                    break;
                default:
                    throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
            }
        }
    

    从代码可以知道,最终会通过判断线程,来依次消费事件。
    查看方法invokeSubscriber(subscription, event);

    void invokeSubscriber(Subscription subscription, Object event) {
            try {
                subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
            } catch (InvocationTargetException e) {
                handleSubscriberException(subscription, event, e.getCause());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Unexpected exception", e);
            }
        }
    

    可以看出最终是以反射的方式。

    3.3 反注册

    注册消费完事件后,我们需要反向注册,类似如我们广播的使用。

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

    可以看到是依次移除掉subscriptions列表。

    4、总结

    从整个代码流程来看,基本上没什么难点,用起来也非常方便。
    但在使用过程中,像我这种菜鸟发现了两个问题

    1. 其消息给人感觉是一种乱跳的感觉,因为其采用注解的方式,这点感觉对业务逻辑梳理并不一定占有优势,就拿Android Studio来说,居然会提示该方法无处使用,如下图:


      no_use.png
    2. 采用反射方法invokeSubscriber来消费事件,效率如何。

    相关文章

      网友评论

        本文标题:EventBus原理与源码解析

        本文链接:https://www.haomeiwen.com/subject/xbgctqtx.html