EventBus 超详细的源码分析学习

前言

EventBus已经使用了那么久啦 ,但是一直都没有去了解过其中内部的机制,后来虽然看了一些博客,但是介绍的内容虽然看过了,但是还是不能很清晰的知道内部实现原理。所以本着 纸上得来终觉浅 绝知此事要躬行 的原则,决定亲自去看一看源码。

内容部分
注册方法分析
//注册的类的注册的事件的方法集合
private final SubscriberMethodFinder subscriberMethodFinder;
//注册方法,这里会存储你注册的对象,里所有的方法(注册监听的方法)  
public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
    //这里是以类为key获取所有该类里的方法。(注册监听的方法)
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                //对所有的方法遍历进行subscribe(建立订阅)
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

这里遍历订阅过程需要在同步锁里进行,为了防止重复注册,导致消息接收重复。

//SubscriberMethod 类记录了订阅方法的信息
  	final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;

subscribe方法内进行一系列的操作,这里主要的工作如下:

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //事件类型,应该解释为事件类型。 这个类型是你注册的方法里的参数类
        Class<?> eventType = subscriberMethod.eventType;
        //这里是把方法和方法的持有对象关联起来
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //拿到同一事件类型的一个集合,如果发送事件需要把集合内的方法都调用,如果没有见加入到map中
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            //每个方法都放到map中
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //如果已经注册过了,就抛出异常,只允许注册一次。每个事件类型key只对应一个结合,如果你的类已经和这个事件关联了,就不能重复注册了,否则就会收到重复的消息。
            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) {
            //继承关系的要处理父类和子类
            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>).
                //粘性事件map
                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. 获取注册类下所有的注册事件的方法,如activity注册了event的监听,就是获取到所有该activity下添加注解标记的方法。

  2. 将每个方法放到不同的列表中进行存储,存储的为map。key为事件类型,value为所有注册该事件的方法的包装类(Subscription)。类中中包含了一些必要信息。如该方法的持有者。(将注册了EventBus的类里注册不同的EventType的方法放到不同的map中存储。key为EventType,value为一个Subscriber的列表)

  3. 后续对事件的优先级进行调整,就是调整事件在list中的index

  4. 将类中的所有注册的事件也统计起来,key为类,value为里面的事件集合,typesBySubscriber这个map中是存储了每个Subscriber中订阅的EventType类型

  5. 粘性事件单独存储,不做过多介绍

以上为注册eventbus所做的工作,

这里留一个问题,这个注册方法调用时机是如何?是在类初始化的时候调用?

因为我没有看过以前的版本,现在的版本实现是编译时注解来完成,注解主要完成的工作是收集每个类中的注解方法信息(包括方法名,所处于的线程,是否粘性等),然后在和类建立一些关系,下面我就简单结合源码梳理一下主流程。

从注解的入口方法开始,就不具体介绍注解类型,处理器实现步骤的一些问题了,EventBusAnnotationProcessor是event的注解处理器,核心的方法都在这里面。

  1. 直接看process(Set<? extends TypeElement> annotations, RoundEnvironment env)方法,所有注册了的注解都会在这里,但是我们只关心Subscribe注解并且是在方法上的,这里需要一些判断过程,判断完成后才会开始正式进行收集操作。

  2. 这里开始收集每个类里面添加的注解内容,看下面的源码部分,

    //process()方法总调用如下方法,名字很清楚就说收集订阅类
    collectSubscribers(annotations, env, messager);
    //具体的搜集操作在这个地方
    private void collectSubscribers(Set<? extends TypeElement> annotations, //这个TypeElement对应的上一个类(网上查的)
            for (TypeElement annotation : annotations) {
                //元素其实上包含注解信息的内容,也上我们的数据源头
                Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
                //便利类的元素集,因为类中会包含很多注解,如butterknife,dagger等
                for (Element element : elements) {
                    //找到方法上的注解元素
                    if (element instanceof ExecutableElement) {
                        ExecutableElement method = (ExecutableElement) element;
                        //这个检查一些配置,如方法上不是静态,是不是publish,如果是集合至少包含一个参数
                        if (checkHasNoErrors(method, messager)) {
                            TypeElement classElement = (TypeElement) method.getEnclosingElement();
                            //这个map把类和注解方法关联起来了。
                            methodsByClass.putElement(classElement, method);
                        }
                    } else {
                        messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                    }
                }
            }
        }
    
    
  3. 下面对每个订阅者中的方法做一些校验,比如方法的修饰符如果是private的话,就要添加到一个需要跳过的集合中,因为发送消息后,调用订阅者中的方法需要通过反射来完成,所以所有的方法都需要是public的才可以,代码如下:

    //检查订阅者跳过,方法名也很明确,具体内容了解一下,不做详细的解释了。
    checkForSubscribersToSkip(messager, indexPackage);
    /**
         * Subscriber classes should be skipped if their class or any involved event class are not visible to the index.
         */
        private void checkForSubscribersToSkip(Messager messager, String myPackage) {
            for (TypeElement skipCandidate : methodsByClass.keySet()) {
                TypeElement subscriberClass = skipCandidate;
                while (subscriberClass != null) {
                    //不是显示的方法就添加到跳过的列表中了,比如是用private修饰
                    if (!isVisible(myPackage, subscriberClass)) {
                        boolean added = classesToSkip.add(skipCandidate);
                        if (added) {
                            String msg;
                            if (subscriberClass.equals(skipCandidate)) {
                                msg = "Falling back to reflection because class is not public";
                            } else {
                                msg = "Falling back to reflection because " + skipCandidate +
                                        " has a non-public super class";
                            }
                            messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
                        }
                        break;
                    }
                    List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
                    if (methods != null) {
                        for (ExecutableElement method : methods) {
                            String skipReason = null;
                            //方法第一个参数(event类型)
                            VariableElement param = method.getParameters().get(0);
                            TypeMirror typeMirror = getParamTypeMirror(param, messager);
                            if (!(typeMirror instanceof DeclaredType) ||
                                    !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                                skipReason = "event type cannot be processed";
                            }
                            if (skipReason == null) {
                                TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                                if (!isVisible(myPackage, eventTypeElement)) {
                                    skipReason = "event type is not public";
                                }
                            }
                            if (skipReason != null) {
                                boolean added = classesToSkip.add(skipCandidate);
                                if (added) {
                                    String msg = "Falling back to reflection because " + skipReason;
                                    if (!subscriberClass.equals(skipCandidate)) {
                                        msg += " (found in super class for " + skipCandidate + ")";
                                    }
                                    messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
                                }
                                break;
                            }
                        }
                    }
                    subscriberClass = getSuperclass(subscriberClass);
                }
            }
        }
    
    
  4. 下面是最后的一个步骤,就是创建我们的代理文件了,这里就比较简单了,主要是每个类都生成一个SubscriberInfo类,里面封装了类的Class文件,类中注册的方法集合超类的信息。然后存入一个static final map中,供使用的地方调用,这样通过代理类来收集信息基本上就不损耗性能了,这也是现在知名的注解框架都是使用这种编译期注解的原因了。

     if (!methodsByClass.isEmpty()) {
                    createInfoIndexFile(index);
                } else {
                    messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
                }
    

    创建文件的方法内容比较常规,就是写Java文件,这里的内容比较常规,就是一行一行的写,写完后就会在指定的目录生成一个Java文件了,这就是我们所需要的代理文件,这个代理文件帮助我们做了收集注解类,注解方法等一系列的工作,让我们可以直接拿到数据直接使用。

  5. 下面这个就是生成的文件,整体比较简单,主要还是收集东西:

    /** This class is generated by EventBus, do not edit. */
    public class MyEventBusIndex implements SubscriberInfoIndex {
        //主要就是为了收集这个动态的集合,以类的class为key,value是SubscriberInfo,上面提到过这里包含了你需要的信息。
        private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
        static {
            SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    
            putIndex(
                    new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
            }));
    
            putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                    true, new SubscriberMethodInfo[] {
                new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
            }));
    //省略一些类似内容
        }
    
        private static void putIndex(SubscriberInfo info) {
            SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
        }
    
        @Override
        public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
            SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
            if (info != null) {
                return info;
            } else {
                return null;
            }
        }
    }
    
    

以上基本就是eventbus通过编译时注解生成代理类完成注解搜集的过程了,流程比较简单和清晰,但是相对以前的运行时注解性能上差了很多。通过学习我们会发现注解能给我们带来很多便利,对解耦合有很大的帮助,这也是现在主流框架都会看到注解影子的原因。


普通事件发送流程分析

post是发送事件的入口,所以开始从post的方法开始分析,代码如下:

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 {
                //类似handler的loop机制,无限的取消息
                while (!eventQueue.isEmpty()) {
                    //一直从0位置开始取消息
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //异常就切换线程,取消发送
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

上面的PostingThreadState记录一些常用的状态内容。

主要内容是发送的内容postSingleEvent()方法的执行。

下面是对该方法的一些解读,注释的很详细了:


    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        //发送发消息的类的字节码
        Class<?> eventClass = event.getClass();
        //默认找不到订阅者
        boolean subscriptionFound = false;
        //默认找父类和子类
        if (eventInheritance) {
            //查找所有的实现事件类型的类包括父类,找到实现了这个事件的所有类
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            //拿到总的数量
            int countTypes = eventTypes.size();

            for (int h = 0; h < countTypes; h++) {
                //拿到注册该post事件类的字节码了
                Class<?> clazz = eventTypes.get(h);
                //调用post方法,找到接收该事件的订阅者了
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            //调用post方法,找到接收该事件的订阅者了
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        //没找到的情况发送一个NoSubscriberEvent事件代替
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

上面一系列操作后,继续调用后续方法,我们快找到真正调用的地方了,继续调用postSingleEventForEventType()方法,这里会返回消息发送成功还是失败。Subscription是比较关键的信息类,里面记录了注册了该事件的所有的类,为后续我们去挨个调用注册类使用。

 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //通过类的字节码,拿到所有的注册事件的类,解释一下,通过你发送的一个event事件,来拿到所有注册了这个事件的类
            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);
                    //默认取消发送为false
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

这里的一系列操作继续向下调用postToSubscription方法,这个方法比较简单,主要是根据事件的所处于的线程来进行不同的分发。代码如下

      private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                //直接就调用方法了,当前线程
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                //主线程的调用,如当前线程不是,需要切换线程(通过handle来做)
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                //有序的,通过handle来做
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                //后台的,通过Runnable来实现的
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                //异步的,通过Runnable来实现的
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

上面代码分的类型比较多,但实际上划分只有两种,一种是常规的发送,一种是特殊处理的发送。特殊处理包括主线程,异步线程,后台线程等。

这里我们可能遇到过一个问题,就是在自线程中发送的消息,如果不在接受的地方表明主线程,会报错。

重点来了啊,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);
        }
    }
结束啦

上面这些关键代码,就完成了一次普通事件的分发流程,其中没有过多介绍细节的实现。

至于粘性事件分发,和普通事件,就差了一个收到事件后需要通知队列,因为如果没有通知,队列的内的粘性消息会一直处于发送状态。

有问题欢迎纠正,谢谢啦

如果对你有帮助就点个赞把,你的鼓励是我前进的动力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值