Eventbus运行流程浅析

2019/06/06 Android

最近项目中比较频繁的运用到了greenbot公司的eventbus框架,怕使用的时候遇见问题无法解决,所以小子最近也是看了点Eventbus的源码解析,了解了一点运行流程,所以特来和大家分享。

首先在了解之前,我们应该先有针对性的提出一些问题,然后针对问题在源码中去寻找答案,否则Eventbus写的内容很多,漫目查找很容易迷失在源码中。我们可以首先来看以下几个问题。

  • Q1.Eventbus有什么好处?我们为什么要去使用它。
  • Q2.Eventbus在设计的时候采用了什么设计模式?或者说什么地方让我们觉得它写得好。
  • Q3.Eventbus事件注册反注册、发送流程、事件触发流程都是怎样实现的?
  • Q4.Eventbus是如何实现事件在多线程中切换触发的?
  • Q5.Eventbus的新特性索引是如何实现性能优化的?

对于源码的切入,我们一般都从构造方法开始。

目录

构造方法

    /**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
     * central bus, consider {@link #getDefault()}.
     */
    public EventBus() {
        this(DEFAULT_BUILDER);  //  构造函数中使用默认的EventbusBuilder
    }

    EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();   Value:Subscription(订阅者和订阅方法的类)
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();  
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        ......
    }

可以看到Eventbus采用了Builder模式来进行参数配置的,构造函数中采用了默认的Builder来构造,同时从源码注释中可知,如果自己用一个新的Eventbus实例那么每个实例都会有一个自己的事件分发域,所以如果你想要统一中央分发事件那么可以使用getDefault().我们再来看下getDefault()是怎么样的。

    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {  // DLC
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

getDefault采用了传统的DLC方法来创建单例的。并且是使用默认的Builder来构建Eventbus单例的。

了解完Eventbus的构造之后,我们再来回到我们的问题中来,我们可以再从Eventbus事件注册流程入手来解决问题。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //  通过订阅者class类找订阅者方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                // 方法订阅
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

Eventbus注册

注册函数,传入的唯一一个参数是Object类型,也就是订阅者。首先通过订阅者的类来找到订阅方法。接着我们来看下findSubscriberMethods是怎么实现的。

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

        if (ignoreGeneratedIndex) { //默认为false
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        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;
        }
    }

这个方法是SubscriberMethodFinder类实现的,首先是从Map缓存中取,如果存储了订阅者类中的订阅方法就直接返回,如果没有存那就会通过findUsingInfo来找,因为ignoreGeneratedIndex属性表示是否忽略注解器生成的MyEventBusIndex,默认为false。找到方法之后便会把方法存储Map缓存中并将订阅者方法返回,我们再来看下findUsingInfo是如何寻找订阅者方法的。

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();   // 从对象池中获取对象。
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            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 {
                // 通过反射找订阅者方法
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        // 通过线程池回收findState并返回订阅者方法
        return getMethodsAndRelease(findState);
    }

通过getSubscriberInfo来获取订阅者方法信息。如果我们通过EventbusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以获取到订阅者方法信息,否则便会通过findUsingReflectionInSingleClass来获取订阅者信息。而EventbusBuilder默认是没有的,所以我们看下findUsingReflectionInSingleClass

    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            // 订阅者方法不是public就抛出异常。
            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) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            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");
            }
        }
    }

通过反射来获取订阅者方法,我们可以看到如果我们的订阅者方法不是public的,那么就会抛出异常must be public, non-staic, and non-abstract。然后通过方法注解来获取事件类型、线程类型、优先级、是否粘性等属性。并且封装成SubscriberMethod对象存在一个list存进findState中。

通过反射找到订阅者方法后,再回到findUsingInfo方法中,而后返回getMethodsAndRelease,该方法中将findState对象回收进对象池中并且返回订阅者方法列表。

到这一步,我们已经知道了注册时找到订阅者中订阅者方法的所有流程,然后接下来在register中又进行了方法的订阅。

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

我们看一下subscribe中又做了什么。

     private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);    //1
        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);
            }
        }

        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);  //2
                break;
            }
        }

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (subscriberMethod.sticky) {
            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)) {   // 3    
                        Object stickyEvent = entry.getValue();
                        // 触发粘性事件
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);   //4
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
  • 注释1中通过事件类型拿到已订阅该事件类型的所有订阅方法和订阅者的封装对象集合。
  • 注释2中,根据方法的优先级将依次插入订阅者方法list中,优先级越高在list中越前面。
  • 注释3中的isAssignableFrom()判断触发事件方法类型是否为粘性事件方法类型的父类,如果子类为粘性事件,那么注册父类方法的时候同样会触发子类的粘性事件。也就是说用子类事件去发送一个粘性事件,然后一个订阅者含有父类的订阅事件(@Subscribe(Eventtype = Parent.class)),在订阅者register()的时候,这个父类的注册方法会触发。
  • 注释4中如果方法为粘性的,那么调用checkPostStickyEventToSubscription来触发,方法内是根据postToSubscription来触发的,我们看下是如何触发的。
      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 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);
         }
     }
    

可以看到通过4中线程模式来分别触发,但都通过invokeSubscriber来触发。我们可以看下。

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

很明显,是通过java反射来触发粘性事件的。而四种线程模式触发情景也一目了然。

  • POSTING:直接触发,触发事件和注册发生在同一线程中。
  • MAIN:如果当前线程(注册的线程)在主线程中就直接触发,如果不是那么就将时间插入到队列中通过Handler来将事件在主线程中触发。
  • BACKGROUND:如果当前线程是主线程,那么通过将事件插入队列中,然后通过线程池来将事件在新的子线程中触发,如果非主线程则直接触发事件。
  • ASYNC:无论当前线程是主线程与否,将事件插入队列中通过线程池在新的子线程中触发。

到这里,我们可以先总结下Eventbus的注册做了哪些事情。

  • 1.通过订阅者的class类来找到该订阅者中的方法,并且在找的过程中将订阅者的class类别,订阅者中的订阅者方法,订阅者方法中包含了哪些事件类型都进行了存储缓存。
  • 2.拿到订阅者方法后,去进行粘性事件的触发操作。并且,如果粘性事件没有进行remove的话,那么每次订阅者重新订阅的时候都会去进行粘性事件的触发。

Eventbus的注册流程已经大致清楚了,而反注册流程其实只是将订阅者注册时存储的list,map表等缓存信息中将该订阅者移除。我们也不再细究。

Eventbus的事件发送

回到开头的问题中,了解了Eventbus的注册和反注册之后,我们再来看下Eventbus的发送流程是怎么样的,也就是post函数如何。

    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

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

先从ThreadLocal中拿取当前线程的post状态,并将事件插入到事件队列中。然后通过postSingleEvent来发送事件,并且将post状态改为true。

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // eventInheritance表示是否向上查找父类事件,默认为true。
        // 所以如果发送子类事件,父类方法也能收到。
        if (eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);    //找所有事件类型
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

找到所有事件类型的list,例如你post的是子类,那么得到的list中包含该子类以及其所有父类。然后事件会通过postSIngleEventForEventType发送到每一个类中。

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    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;
    }

这里通过我们之前在粘性事件中提到的postToSubscription来触发事件。所以说如果发送了某个事件,那么会通知到订阅者中该事件类型的订阅方法,同时也会通知到订阅者中该事件类型所有父类的订阅方法。

到这里我们就已经都清楚了Eventbus的构造、注册反注册、事件发送以及事件触发是如何进行的了。那么这也解答了问题3中的问题。同时在分析的过程中我们也能解答出问题1、2。

接下来,我们考虑一下最后一个问题。Eventbus的索引是如何实现的以及是如何优化的。
我们之前有提到过MyEventBusIndex索引。我们在使用索引之前,需要先在build.gradle中配置一下。

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [eventBusIndex: 'com.example.MyEventBusIndex']
            }
        }

        dependencies {
            // Eventbus库
            implementation 'org.greenrobot:eventbus:3.1.1'
            // Eventbus注释编译器
            annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
        }

然后再自定义的Application中加入。

    EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

这样的话就可以使用Eventbus的索引功能了。在我们在gradle中配置好了之后,在APP运行时会自动生成索引类MyEventBusIndex(名字和gradle中配置的一样)。
我们可以来看下生成的索引类是怎么样的。

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.esunny.estar.EsNavFragment.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("Event", com.esunny.data.api.event.EsEventMessage.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(com.esunny.estar.MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("Event", com.esunny.data.api.event.EsEventMessage.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(com.esunny.estar.StartActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("quoteEvent", com.esunny.data.api.event.QuoteEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("hisQuoteEvent", com.esunny.data.api.event.HisQuoteEvent.class, ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    ......
}

可以看到系统生成的索引类中主要维护了一个Map。Map中含有类和对应其中的方法。当你的方法有@Subscribe的注释时就会自动添加进入。而且我们之前findUsingInfo()的时候就直接通过索引从map中拿到方法列表,而不是通过反射去获取。所以这样就大大提高了Eventbus的性能。

总结

对于问题1,Eventbus有什么好处,我们为什么要用它?

  • 1.Eventbus将订阅者和发送者进行了解耦合操作,订阅者只需要实现注册及订阅方法的实现,而不用去关注何处发送事件。
  • 2.Eventbus可以用于组件间通信,并且可以携带大容量数据。在传统的Activity通信的Intent中仅仅允许进行1MB大小的传输,而采用Eventbus却可以携带更大的数据。
  • 3.采用了粘性事件和优先级的处理,使事件接收时不需要已注册(粘性事件可以注册时再接收)。

对于问题2,Eventbus采用了什么设计模式以及哪里写的好?

  • 1.从之前的分析中我们看到了Eventbus的构造时采用的Builder模式进行构建与显示进行了分析操作
  • 2.使用的时候采用了单例模式。
  • 3.发送及接收的整体流程其实是一种观察者模式。
  • 4.构建findState对象时采用了对象池来创建和回收对象
  • 5.线程切换触发事件时采用了newCachedThreadPool构建线程池来进行处理。

问题3,事件注册反注册、发送流程、事件触发流程从上面的分析也清楚了。

问题4,是如何实现事件在多线程中切换触发的? Eventbus通过4种线程模式来触发事件,通过Handler及线程池来进行事件在主线程和子线程中的切换,以及线程池将事件弄到子线程中进行触发。

问题5,Eventbus通过索引配置会自动生成索引类,并且索引类中会搜寻项目中包含@Subscribe的注释方法组成Map。当Eventbus需要获取方法的时候可以直接从Map中得到,而不用通过反射来获取,因此大大提升了Eventbus的性能。

引用

Search

    Table of Contents