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