美文网首页Android开发Android学习安卓
Android 主流开源框架(八)EventBus 源码解析(含

Android 主流开源框架(八)EventBus 源码解析(含

作者: wildma | 来源:发表于2020-11-29 15:10 被阅读0次

    前言

    最近有个想法——就是把 Android 主流开源框架进行深入分析,然后写成一系列文章,包括该框架的详细使用与源码解析。目的是通过鉴赏大神的源码来了解框架底层的原理,也就是做到不仅要知其然,还要知其所以然。

    这里我说下自己阅读源码的经验,我一般都是按照平时使用某个框架或者某个系统源码的使用流程入手的,首先要知道怎么使用,然后再去深究每一步底层做了什么,用了哪些好的设计模式,为什么要这么设计。

    系列文章:

    更多干货请关注 AndroidNotes

    一、使用示例

    1.1 基本用法

    (1)定义事件
    这个事件就是需要传递的实体类。

    public class MessageEvent {
    
        public final String message;
    
        public MessageEvent(String message) {
            this.message = message;
        }
    }
    

    (2)准备订阅者
    声明用来处理事件的订阅方法。

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }
    

    (3)注册与解除注册
    注册与解除注册订阅者,与第(2)步放在同一个类中。

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }
    
    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this);
    }
    

    (4)发送事件
    可以在任何地方发送事件。

    EventBus.getDefault().post(new MessageEvent("普通事件"));
    

    1.2 粘性事件

    普通事件是先注册和准备订阅者,然后再发送事件才能收到。而粘性事件在发送事件之后再注册和准备订阅者也能收到事件。

    使用上与普通事件的区别是第(2)步需要增加 “sticky = true” 声明为粘性事件,第(4)步 post 方法改成 postSticky 方法,如下:

    准备订阅者:

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(StickyMessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }
    

    发送事件:

    EventBus.getDefault().postSticky(new StickyMessageEvent("粘性事件"));
    

    接下来我们就根据这几步进行源码分析。

    二、源码分析

    源码版本:3.2.0

    2.1 Subscribe 注解

    /*Subscribe*/
    public @interface Subscribe {
        //(1)
        ThreadMode threadMode() default ThreadMode.POSTING;
        //(2)
        boolean sticky() default false;
        //(3)
        int priority() default 0;
    }
    

    源码中我标注了 3 个关注点,分别如下:

    • (1):指定线程模式,即指定订阅方法在哪个线程执行,线程模式一共有 5 种:POSTING(默认模式)、MAIN、MAIN_ORDERED、BACKGROUND、ASYNC,关于它们的区别放到后面再分析。
    • (2):是否是粘性事件
    • (3):指定优先级,默认为 0。在相同线程模式下,优先级越高的订阅方法越先收到事件。

    以上设置都是在 “准备订阅者” 那里配置的,如下:

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true, priority = 1)
    public void onMessageEvent(StickyMessageEvent event) {
        Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
    }
    

    2.2 注册

    在看注册方法之前我们先看下是怎么获取 EventBus 实例的,即 EventBus#getDefault() 中的 getDefault 方法:

        /*EventBus*/
        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*/
        private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
        private final Map<Object, List<Class<?>>> typesBySubscriber;
        private final Map<Class<?>, Object> stickyEvents;
    
        public EventBus() {
            this(DEFAULT_BUILDER);
        }
    
        EventBus(EventBusBuilder builder) {
            logger = builder.getLogger();
            //(1)start
            subscriptionsByEventType = new HashMap<>();
            typesBySubscriber = new HashMap<>();
            stickyEvents = new ConcurrentHashMap<>();
            //(1)end
    
            mainThreadSupport = builder.getMainThreadSupport();
            //(2)
            mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
            //(3)
            backgroundPoster = new BackgroundPoster(this);
            //(4)
            asyncPoster = new AsyncPoster(this);
            indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
            //(5)
            subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                    builder.strictMethodVerification, builder.ignoreGeneratedIndex);
            /*一些 boolean 类型的常量赋值*/
            logSubscriberExceptions = builder.logSubscriberExceptions;
            logNoSubscriberMessages = builder.logNoSubscriberMessages;
            sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
            sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
            throwSubscriberException = builder.throwSubscriberException;
            eventInheritance = builder.eventInheritance;
    
            //(6)
            executorService = builder.executorService;
        }
    

    首先是调用无参构造方法,然后传入一个 DEFAULT_BUILDER 来调用有参构造,这个 DEFAULT_BUILDER 是 EventBusBuilder,它是一个 EventBus 的建造器,里面封装了一些 EventBus 所需要的参数等。
    源码中我标记了 6 个关注点,分别如下:

    • (1):可以看到这里创建的 3 个对象都是 map 集合,具体保存了什么这里看不出来,我们后面再分析。
    • (2):创建了一个 mainThreadPoster 对象,该对象主要用来将线程模型为 MAIN 和 MAIN_ORDERED 的事件加入到队列中,然后进行处理。点击 createPoster 方法进去看看是怎么创建的:
    /*MainThreadSupport*/
    public interface MainThreadSupport {
    
        ...
    
        Poster createPoster(EventBus eventBus);
    
        class AndroidHandlerMainThreadSupport implements MainThreadSupport {
    
            ...
    
            @Override
            public Poster createPoster(EventBus eventBus) {
                return new HandlerPoster(eventBus, looper, 10);
            }
        }
    
    }
    
    /*HandlerPoster*/
    public class HandlerPoster extends Handler implements Poster {
        ...
    }
    

    可以看到这里创建了一个 HandlerPoster,所以这里的 mainThreadPoster 实际上是一个 HandlerPoster。

    • (3)创建了一个 BackgroundPoster 对象,该对象主要用来将线程模型为 BACKGROUND 的事件加入到队列中,然后进行处理。
    • (4)创建了一个 AsyncPoster 对象,该对象主要用来将线程模型为 ASYNC 的事件加入到队列中,然后进行处理。
    • (5)创建了一个 SubscriberMethodFinder 对象,该对象主要用来获取注册类上所有订阅方法的集合。
    • (6)从 EventBusBuilder 中取出一个创建好的线程池(CachedThreadPool)赋值给 executorService。

    接下来看下 register 方法:

        /*EventBus*/
        public void register(Object subscriber) {
            Class<?> subscriberClass = subscriber.getClass();
            //(1)
            List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
            synchronized (this) {
                //(2)
                for (SubscriberMethod subscriberMethod : subscriberMethods) {
                    subscribe(subscriber, subscriberMethod);
                }
            }
        }
    

    源码中我标记了 2 个关注点,分别如下:

    • EventBus#register() 中的关注点(1)——findSubscriberMethods()
      主要是获取注册类上所有订阅方法的集合,也就是使用示例中 “准备订阅者” 那步加了 @Subscribe 注解的那些方法。点进去看看:
        /*SubscriberMethodFinder*/
        private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
        private final boolean ignoreGeneratedIndex;
    
        List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
            //(1)
            List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
            if (subscriberMethods != null) {
                return subscriberMethods;
            }
    
            //(2)
            if (ignoreGeneratedIndex) {
                subscriberMethods = findUsingReflection(subscriberClass);
            } else {
                //(3)
                subscriberMethods = findUsingInfo(subscriberClass);
            }
            // 订阅方法为空,说明注册类中不存在 @Subscribe 注解的方法。
            if (subscriberMethods.isEmpty()) {
                throw new EventBusException("Subscriber " + subscriberClass
                        + " and its super classes have no public methods with the @Subscribe annotation");
            } else {
                //(4)
                METHOD_CACHE.put(subscriberClass, subscriberMethods);
                return subscriberMethods;
            }
        }
    

    源码中我标记了 4 个关注点,分别如下:

    • (1):从 METHOD_CACHE 集合中查找是否已经缓存了该注册类上的订阅方法。
    • (2):ignoreGeneratedIndex 是在 EventBus 的有参构造中通过 EventBusBuilder 配置的,我们没配置,默认是 false,所以直接走关注点(3)。
    • (3):查找所有订阅方法。
    • (4):找到所有订阅方法后缓存到 METHOD_CACHE 集合中。

    下面具体看看关注点(3)中内部是怎么实现的:

        /*SubscriberMethodFinder*/
        private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
            //(1)
            FindState findState = prepareFindState();
            //(2)
            findState.initForSubscriber(subscriberClass);
            while (findState.clazz != null) {
                //(3)
                findState.subscriberInfo = getSubscriberInfo(findState);
                // 默认 subscriberInfo 为 null,不会走这里
                if (findState.subscriberInfo != null) {
                    SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                    for (SubscriberMethod subscriberMethod : array) {
                        if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                            findState.subscriberMethods.add(subscriberMethod);
                        }
                    }
                } else {
                    //(4)
                    findUsingReflectionInSingleClass(findState);
                }
                findState.moveToSuperclass();
            }
            //(5)
            return getMethodsAndRelease(findState);
        }
    

    源码中我标记了 5 个关注点,分别如下:

    • (1):调用 prepareFindState 方法返回一个 FindState 对象,看一下 FindState:
    /*SubscriberMethodFinder*/
    class SubscriberMethodFinder {
        static class FindState {
            /*查找过程中临时用来记录订阅方法等的一些集合*/
            final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
            final Map<Class, Object> anyMethodByEventType = new HashMap<>();
            final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
    
            final StringBuilder methodKeyBuilder = new StringBuilder(128);
    
            /*临时用来记录的一些变量*/
            Class<?> subscriberClass;
            Class<?> clazz;
            boolean skipSuperClasses;
            SubscriberInfo subscriberInfo;
    
            void initForSubscriber(Class<?> subscriberClass) {
                this.subscriberClass = clazz = subscriberClass;
                skipSuperClasses = false;
                subscriberInfo = null;
            }
    
            // 查找完成进行回收
            void recycle() {
                subscriberMethods.clear();
                anyMethodByEventType.clear();
                subscriberClassByMethodKey.clear();
                methodKeyBuilder.setLength(0);
                subscriberClass = null;
                clazz = null;
                skipSuperClasses = false;
                subscriberInfo = null;
            }
    
            ...
    
        }
    }
    

    可以看到,FindState 是 SubscriberMethodFinder 中的静态类,主要用来辅助查找订阅方法。

    继续看下 prepareFindState 方法是怎么返回 FindState 对象的:

        /*SubscriberMethodFinder*/
        private FindState prepareFindState() {
            synchronized (FIND_STATE_POOL) {
                for (int i = 0; i < POOL_SIZE; i++) {
                    FindState state = FIND_STATE_POOL[i];
                    if (state != null) {
                        FIND_STATE_POOL[i] = null;
                        return state;
                    }
                }
            }
            return new FindState();
        }
    

    首先从 FIND_STATE_POOL 中取出可用的 FindState,如果没有则重新创建一个 FindState。

    • (2):初始化刚刚 FindState 类中的那些变量。

    • (3):点击 getSubscriberInfo 方法进去看看:

        /*SubscriberMethodFinder*/
        private SubscriberInfo getSubscriberInfo(FindState findState) {
            if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
                SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
                if (findState.clazz == superclassInfo.getSubscriberClass()) {
                    return superclassInfo;
                }
            }
            if (subscriberInfoIndexes != null) {
                for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                    SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                    if (info != null) {
                        return info;
                    }
                }
            }
            return null;
        }
    

    其中 subscriberInfo 是刚刚关注点(2)中初始化的 null,subscriberInfoIndexes 是在 EventBus 的有参构造中通过 EventBusBuilder 配置的,我们没配置,也为 null,所以这个方法默认只会返回 null。

    • (4):点击 findUsingReflectionInSingleClass 方法进去看看:
        /*SubscriberMethodFinder*/
        private void findUsingReflectionInSingleClass(FindState findState) {
            Method[] methods;
            try {
                // 反射获取注册类中的所有方法
                methods = findState.clazz.getDeclaredMethods();
            } catch (Throwable th) {
                try {
                    // 反射获取注册类中的所有方法
                    methods = findState.clazz.getMethods();
                } catch (LinkageError error) {
                    ...
                }
                findState.skipSuperClasses = true;
            }
            // 循环遍历所有方法
            for (Method method : methods) {
                int modifiers = method.getModifiers();
                if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length == 1) {
                        // 获取方法上的注解
                        Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                        if (subscribeAnnotation != null) {
                            // 获取方法上的第一个参数,也就是事件类型(即使用示例中 MessageEvent 的类型)
                            Class<?> eventType = parameterTypes[0];
                            if (findState.checkAdd(method, eventType)) {
                                // 获取线程模式
                                ThreadMode threadMode = subscribeAnnotation.threadMode();
                                // 将方法、事件类型、线程模式、优先级、是否是粘性事件封装到 SubscriberMethod 对象,然后添加到 FindState 类中的 subscriberMethods 集合中存起来
                                findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                        subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                            }
                        }
                    } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                        String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                        throw new EventBusException("@Subscribe method " + methodName +
                                "must have exactly 1 parameter but has " + parameterTypes.length);
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException(methodName +
                            " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
                }
            }
        }
    

    上面的注释写的很清楚了,主要是通过反射获取注册类中的所有方法,然后遍历方法,判断如果带有 Subscribe 注解的方法,就将该方法、事件类型、线程模式、优先级、是否是粘性事件封装到 SubscriberMethod 对象,然后添加到 FindState 类中的 subscriberMethods 集合中存起来。

    • (5):点击 getMethodsAndRelease 方法进去看看:
        /*SubscriberMethodFinder*/
        private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
            List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
            findState.recycle();
            synchronized (FIND_STATE_POOL) {
                for (int i = 0; i < POOL_SIZE; i++) {
                    if (FIND_STATE_POOL[i] == null) {
                        FIND_STATE_POOL[i] = findState;
                        break;
                    }
                }
            }
            return subscriberMethods;
        }
    

    可以看到,这里首先从 FindState 中取出保存的所有订阅方法的信息,这时候 FindState 就没用了,那么需要调用 recycle 方法将里面的一些集合、变量等进行回收。但是 FindState 经常要用到,所以会把它缓存到 FIND_STATE_POOL 中,这样下次就不用重新创建 FindState 了,提高性能。最后将 subscriberMethods 返回。

    这样 findSubscriberMethods 方法就看完了,继续看 EventBus#register() 中的关注点(2),即 subscribe 方法。

    • EventBus#register() 中的关注点(2)——subscribe()
      主要是循环获取每个订阅方法进行订阅,实际内部只是给 subscriptionsByEventType、typesBySubscriber 集合添加数据。点进去看看:
        /*EventBus*/
        private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
            // 获取事件类型
            Class<?> eventType = subscriberMethod.eventType;
            //(1)
            Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
            //(2)start
            CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
            if (subscriptions == null) {
                subscriptions = new CopyOnWriteArrayList<>();
                subscriptionsByEventType.put(eventType, subscriptions);
            } else {
                if (subscriptions.contains(newSubscription)) {
                    throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                            + eventType);
                }
            }
            //(2)end
    
            int size = subscriptions.size();
            //(3)
            for (int i = 0; i <= size; i++) {
                if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                    subscriptions.add(i, newSubscription);
                    break;
                }
            }
    
            //(4)start
            List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
            if (subscribedEvents == null) {
                subscribedEvents = new ArrayList<>();
                typesBySubscriber.put(subscriber, subscribedEvents);
            }
            subscribedEvents.add(eventType);
            //(4)end
    
            //(5)
            if (subscriberMethod.sticky) {
                ...
            }
        }
    

    源码中我标注了 5 个关注点,分别如下:

    • (1):将注册类与订阅方法封装到 Subscription。
    • (2):subscriptionsByEventType 就是一开始在 EventBus 有参构造方法中创建的 HashMap,终于用到了!这里首先通过 eventType 去 subscriptionsByEventType 中查找是否已经有 Subscription 集合,没有则创建一个,然后添加到 subscriptionsByEventType 中。所以 subscriptionsByEventType 是一个存储 key 为事件类型,value 为 Subscription 集合的 HashMap
    • (3):根据设置的优先级将 newSubscription 添加到 subscriptions 集合中,也就是将优先级越高的排在集合中越前的位置。
    • (4):typesBySubscriber 也是一开始在 EventBus 有参构造方法中创建的 HashMap,这里首先通过 subscriber 去 typesBySubscriber 中查找是否已经有 eventType 集合,没有则创建一个,然后添加到 typesBySubscriber 中,最后一句是添加事件到事件集合中。所以 typesBySubscriber 是一个存储 key 为注册类(对应使用示例中的 EventBusFirstActivity),value 为事件类型集合的 HashMap。typesBySubscriber 的作用是判断某个对象是否注册过,防止重复注册。也就是下面这个方法:
        /*EventBus*/
        public synchronized boolean isRegistered(Object subscriber) {
            return typesBySubscriber.containsKey(subscriber);
        }
    
    • (5):粘性事件相关逻辑,放到最后再分析。

    这样,注册方法也就分析完了。

    小结
    通过反射获取注册类上所有的订阅方法,然后将这些订阅方法进行包装保存到 subscriptionsByEventType 集合。这里还用 typesBySubscriber 集合保存了事件类型集合,用来判断某个对象是否注册过。

    2.3 解除注册

    看一下解除注册的 unregister 方法:

        /*EventBus*/
        public synchronized void unregister(Object subscriber) {
            // 根据注册类获取 typesBySubscriber 集合中保存的事件类型集合
            List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
            if (subscribedTypes != null) {
                for (Class<?> eventType : subscribedTypes) {
                    // 看下面
                    unsubscribeByEventType(subscriber, eventType);
                }
                // 移除 typesBySubscriber 中保存的事件类型集合
                typesBySubscriber.remove(subscriber);
            } else {
                logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
            }
        }
    
        /*EventBus*/
        private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
            // 根据事件类型获取 subscriptionsByEventType 集合中保存的 Subscription 集合
            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;
                        // 从 Subscription 集合中移除 Subscription
                        subscriptions.remove(i);
                        i--;
                        size--;
                    }
                }
            }
        }
    

    可以看到,unregister 方法非常简单,上面都有注释了,我们直接来个小结。

    小结
    注册的时候使用 subscriptionsByEventType 集合保存了所有订阅方法信息,使用 typesBySubscriber 集合保存了所有事件类型。那么解注册的时候就是为了移除这两个集合中保存的内容。

    2.4 发送普通事件

    看一下发送事件的 post 方法:

        /*EventBus*/
        private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
            @Override
            protected PostingThreadState initialValue() {
                return new PostingThreadState();
            }
        };
    
        public void post(Object event) {
            //(1)start
            PostingThreadState postingState = currentPostingThreadState.get();
            List<Object> eventQueue = postingState.eventQueue;
            eventQueue.add(event);
            //(1)end
    
            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()) {
                        //(2)
                        postSingleEvent(eventQueue.remove(0), postingState);
                    }
                } finally {
                    postingState.isPosting = false;
                    postingState.isMainThread = false;
                }
            }
        }
    

    源码中我标记了 2 个关注点,分别如下:

    • EventBus#post() 中的关注点(1)

    这里的 currentPostingThreadState 是一个 ThreadLocal 对象,里面保存了 PostingThreadState。使用 ThreadLocal 的好处是保证 PostingThreadState 是线程私有的,其他线程无法访问,避免出现线程安全问题。

    继续看下 PostingThreadState 里面是什么:

    /*EventBus*/
    public class EventBus {
        final static class PostingThreadState {
            final List<Object> eventQueue = new ArrayList<>();
            boolean isPosting;
            boolean isMainThread;
            Subscription subscription;
            Object event;
            boolean canceled;
        }
    }
    

    可以看到,里面有事件队列、Subscription、事件,以及是否正在发送、是否主线程、是否已取消的标记位。

    所以关注点(1)就是从 currentPostingThreadState 中获取 PostingThreadState,然后拿到事件队列,最后将传进来的事件保存到该事件队列中。

    • EventBus#post() 中的关注点(2)——postSingleEvent()

    主要是从事件队列中取出一个事件进行发送,看下里面做了什么:

        /*EventBus*/
        private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
            Class<?> eventClass = event.getClass();
            // 是否找到订阅者
            boolean subscriptionFound = false;
            //(1)
            if (eventInheritance) {
                //(2)
                List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
                int countTypes = eventTypes.size();
                for (int h = 0; h < countTypes; h++) {
                    Class<?> clazz = eventTypes.get(h);
                    //(3)
                    subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
                }
            } else {
                // (4)
                subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
            }
            if (!subscriptionFound) {
                if (logNoSubscriberMessages) {
                    logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
                }
                if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                        eventClass != SubscriberExceptionEvent.class) {
                    //(5)
                    post(new NoSubscriberEvent(this, event));
                }
            }
        }
    

    源码中我标记了 5 个关注点,分别如下:

    • (1):eventInheritance 表示是否需要发送父类与接口中的事件,默认为 true,可在 EventBusBuilder 中配置。
    • (2):点击 lookupAllEventTypes 方法进去看看:
    /*EventBus*/
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
    
        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;
            }
        }
    
        static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
            for (Class<?> interfaceClass : interfaces) {
                if (!eventTypes.contains(interfaceClass)) {
                    eventTypes.add(interfaceClass);
                    addInterfaces(eventTypes, interfaceClass.getInterfaces());
                }
            }
        }
    

    主要是查找所有事件类型,包括当前事件、父类和接口中的事件。这里用了一个 eventTypesCache 集合类保存查找到的事件类型,避免每次都查找,提高性能。

    • (3):根据事件类型发送事件,包括父类、接口。
    • (4):根据事件类型只发送当前注册类的事件,忽略父类以及接口。
    • (5):没有找到订阅者,发送一个 NoSubscriberEvent 事件。

    下面详细分析下关注点(3)的 postSingleEventForEventType 方法:

        /*EventBus*/
        private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
            CopyOnWriteArrayList<Subscription> subscriptions;
            synchronized (this) {
                //(1)
                subscriptions = subscriptionsByEventType.get(eventClass);
            }
            if (subscriptions != null && !subscriptions.isEmpty()) {
                for (Subscription subscription : subscriptions) {
                    postingState.event = event;
                    postingState.subscription = subscription;
                    boolean aborted;
                    try {
                        //(2)
                        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)首先从 subscriptionsByEventType 集合中取出之前注册的时候保存的 Subscription 集合,然后遍历集合拿到 Subscription,然后调用关注点(2)中的 postToSubscription 方法。

    看下 postToSubscription 方法:

        /*EventBus*/
        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);
            }
        }
    

    可以看到,这里是通过我们在注解中设置的线程模式来决定在哪个线程执行订阅方法。下面详细分析下这 5 种线程模式。

    POSTING
    看一下 invokeSubscriber 方法:

        /*EventBus*/
        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);
            }
        }
    

    直接通过反射调用。发送事件的 post 方法我们刚刚一路跟下来发现是没有发生任何线程切换的,因此这里还是发送事件所在的线程。所以,如果线程模式是 POSTING,那么在哪个线程发送事件,就在哪个线程执行订阅方法。

    MAIN
    这里首先判断是否在主线程,在主线程则直接反射调用;否则调用 mainThreadPoster 的 enqueue 方法。我们在 “2.2 注册” 那里已经分析过 mainThreadPoster 实际上是一个 HandlerPoster,所以直接看下 HandlerPoster#enqueue():

    /*HandlerPoster*/
    public class HandlerPoster extends Handler implements Poster {
    
     private final PendingPostQueue queue;
    
        public void enqueue(Subscription subscription, Object event) {
            //(1)
            PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
            synchronized (this) {
                //(2)
                queue.enqueue(pendingPost);
                if (!handlerActive) {
                    handlerActive = true;
                    //(3)
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                }
            }
        }
    }
    

    源码中我标记了 3 个关注点,分别如下:

    • (1):将 subscription 和 event 封装成一个 PendingPost 对象。
    • (2):将 PendingPost 对象加入到队列中。
    • (3):发送一个消息。因为是继承的 Handler,所以这里调用的是 Handler#sendMessage(),这样 handleMessage 方法自然就能被调用。

    我们看下 handleMessage 方法:

    /*HandlerPoster*/
    public class HandlerPoster extends Handler implements Poster {
    
        @Override
        public void handleMessage(Message msg) {
            boolean rescheduled = false;
            try {
                long started = SystemClock.uptimeMillis();
                while (true) {
                    //(1)
                    PendingPost pendingPost = queue.poll();
        ...
                    //(2)
                    eventBus.invokeSubscriber(pendingPost);
        ...
                }
            } finally {
                handlerActive = rescheduled;
            }
        }
    }
    

    可以看到,这里会不断的从队列中取出 PendingPost,然后调用 EventBus 的 invokeSubscriber 方法进行处理。

    看一下这个方法:

        /*EventBus*/
        void invokeSubscriber(PendingPost pendingPost) {
            Object event = pendingPost.event;
            Subscription subscription = pendingPost.subscription;
            PendingPost.releasePendingPost(pendingPost);
            if (subscription.active) {
                invokeSubscriber(subscription, event);
            }
        }
    
        /*EventBus*/
        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);
            }
        }
    

    同样是通过反射调用订阅方法。所以,如果线程模式是 MAIN,那么在主线程发送事件,则在主线程执行订阅方法;否则先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。

    MAIN_ORDERED
    mainThreadPoster 不会为空,所以与 MAIN 一样是调用了 enqueue 方法,只不过这里少了线程的判断。所以,如果线程模式是 MAIN_ORDERED,那么无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。

    BACKGROUND
    这里首先判断是否在主线程,在主线程则调用 backgroundPoster 的 enqueue 方法;否则直接反射调用。我们直接看下 BackgroundPoster:

    /*BackgroundPoster*/
    final class BackgroundPoster implements Runnable, Poster {
    
        ...
    
        public void enqueue(Subscription subscription, Object event) {
            PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
            synchronized (this) {
                queue.enqueue(pendingPost);
                if (!executorRunning) {
                    executorRunning = true;
                    //(1)
                    eventBus.getExecutorService().execute(this);
                }
            }
        }
    
        @Override
        public void run() {
            try {
                try {
                    while (true) {
                        PendingPost pendingPost = queue.poll(1000);
                        ...
                        eventBus.invokeSubscriber(pendingPost);
                    }
                } catch (InterruptedException e) {
                    ...
                }
            } finally {
                executorRunning = false;
            }
        }
    
    }
    

    可以看到,与 MAIN 类似,都是先将 subscription 和 event 封装成一个 PendingPost 对象,然后加入到队列中。不同的是这里不是使用 Handler 发送消息,而是通过线程池去执行。所以,如果线程模式是 BACKGROUND,那么在子线程发送事件,则在子线程执行订阅方法,否则先将事件加入到队列中,然后通过线程池去执行。

    ASYNC
    没有任何线程判断,直接看 AsyncPoster:

    /*AsyncPoster*/
    class AsyncPoster implements Runnable, Poster {
    
        private final PendingPostQueue queue;
        private final EventBus eventBus;
    
        AsyncPoster(EventBus eventBus) {
            this.eventBus = eventBus;
            queue = new PendingPostQueue();
        }
    
        public void enqueue(Subscription subscription, Object event) {
            PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
            queue.enqueue(pendingPost);
            eventBus.getExecutorService().execute(this);
        }
    
        @Override
        public void run() {
            PendingPost pendingPost = queue.poll();
            if(pendingPost == null) {
                throw new IllegalStateException("No pending post available");
            }
            eventBus.invokeSubscriber(pendingPost);
        }
    
    }
    

    与 BackgroundPoster 一样。所以,如果线程模式是 ASYNC,那么无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过线程池去执行。

    小结
    从 subscriptionsByEventType 集合中取出所有订阅方法,然后根据线程模式判断是否需要切换线程,不需要则直接通过反射调用订阅方法;需要则通过 Handler 或线程池切换到指定线程再执行。

    2.5 发送粘性事件

    看一下发送粘性事件的 postSticky 方法:

        /*EventBus*/
        public void postSticky(Object event) {
            synchronized (stickyEvents) {
                stickyEvents.put(event.getClass(), event);
            }
            post(event);
        }
    

    首先将事件保存到 stickyEvents 集合中,然后调用 post 方法发送事件,这个方法与刚刚讲的发送事件的方法是一模一样的,只是这里最终不会把事件发送出去,具体看下面:
    post()——>postSingleEvent()——>postSingleEventForEventType()

        /*EventBus*/
        private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
            CopyOnWriteArrayList<Subscription> subscriptions;
            synchronized (this) {
                subscriptions = subscriptionsByEventType.get(eventClass);
            }
            if (subscriptions != null && !subscriptions.isEmpty()) {
                 ...
                 postToSubscription(subscription, event, postingState.isMainThread);
                 ...
            }
            return false;
        }
    

    这里的 subscriptions 取出来是空的,所以并不会执行 postToSubscription 方法。

    那么怎么将事件发送出去呢?
    我们一开始就说了 “普通事件是先注册和准备订阅者,然后再发送事件才能收到。而粘性事件在发送事件之后再注册和准备订阅者也能收到事件。”,所以是在注册的那里将事件发送出去的,我们分析注册的时候省略了粘性事件相关逻辑,现在回去再看看:

        /*EventBus*/
        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);
                }
            }
        }
    

    这里判断如果是粘性事件,则从 stickyEvents 集合中取出事件,然后调用 checkPostStickyEventToSubscription 方法:

        /*EventBus*/
        private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
            if (stickyEvent != null) {
                postToSubscription(newSubscription, stickyEvent, isMainThread());
            }
        }
    

    这里调用的 postToSubscription 方法就是我们上一节分析过的,也就是根据线程模型决定是否要切换线程执行订阅方法。

    小结
    发送粘性事件的的时候,首先会将事件保存到 stickyEvents 集合,等到注册的时候判断如果是粘性事件,则从集合中取出事件进行发送。

    三、总结

    EventBus 源码相对于 Glide 还是比较简单的,但是里面用到的 ThreadLocal、反射、设计模式等都是值得学习的。最后总结一下面试相关问题,这样也算是看完这篇博客的收获了。

    (1)介绍一下 EventBus 以及它的优点
    EventBus 是一个 Android 事件发布/订阅框架,主要用来简化 Activity、Fragment、Service、线程等之间的通讯。优点是开销小、使用简单、以及解耦事件发送者和接收者。

    (2)为什么要使用 EventBus 来替代广播呢?

    • 广播:广播是重量级的,消耗资源较多的方式。如果不做处理也是不安全的。
    • EventBus:开销小、使用简单、以及解耦事件发送者和接收者。

    (3)说下 5 种线程模式的区别

    • POSTING:默认模式,在哪个线程发送事件,就在哪个线程执行订阅方法。
    • MAIN:如果在主线程发送事件,则在主线程执行订阅方法;否则先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。
    • MAIN_ORDERED:无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。
    • BACKGROUND:如果在子线程发送事件,则在子线程执行订阅方法,否则先将事件加入到队列中,然后通过线程池去执行。
    • ASYNC:无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过线程池去执行。

    (4)EventBus 是如何做到发送粘性消息的?
    发送粘性事件的的时候,首先会将事件保存到 stickyEvents 集合,等到注册的时候判断如果是粘性事件,则从集合中取出事件进行发送。

    (5)说下 EventBus 的原理

    • 注册
      通过反射获取注册类上所有的订阅方法,然后将这些订阅方法进行包装保存到 subscriptionsByEventType 集合。这里还用 typesBySubscriber 集合保存了事件类型集合,用来判断某个对象是否注册过。
    • 解注册
      注册的时候使用 subscriptionsByEventType 集合保存了所有订阅方法信息,使用 typesBySubscriber 集合保存了所有事件类型。那么解注册的时候就是为了移除这两个集合中保存的内容。
    • 发送普通事件
      从 subscriptionsByEventType 集合中取出所有订阅方法,然后根据线程模式判断是否需要切换线程,不需要则直接通过反射调用订阅方法;需要则通过 Handler 或线程池切换到指定线程再执行。
    • 发送粘性事件
      发送粘性事件的的时候,首先会将事件保存到 stickyEvents 集合,等到注册的时候判断如果是粘性事件,则从集合中取出事件进行发送。

    关于我

    我是 wildmaCSDN 认证博客专家简书程序员优秀作者,擅长屏幕适配
    如果文章对你有帮助,点个赞就是对我最大的认可!

    相关文章

      网友评论

        本文标题:Android 主流开源框架(八)EventBus 源码解析(含

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