Eventbus运行流程浅析

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

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

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

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

文章出自: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)) {
                       Object stickyEvent = entry.getValue();
                       // 触发粘性事件
                       checkPostStickyEventToSubscription(newSubscription, stickyEvent);   //3
                   }
               }
           } else {
               Object stickyEvent = stickyEvents.get(eventType);
               checkPostStickyEventToSubscription(newSubscription, stickyEvent);
           }
       }
   }

注释1中通过订阅者类拿到之前该订阅者类中的订阅方法。注释2中,根据方法的优先级将依次插入订阅者方法list中,优先级越高在list中越前面。注释3中如果方法为粘性的,那么调用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。

总结

对于问题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及线程池来进行事件在主线程和子线程中的切换,以及线程池将事件弄到子线程中进行触发。

引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值