EventBus源码解析之事件分发

  • 前言

    本文主要介绍注册时候的事件分发逻辑以及调用者直接使用post触发的事件分发逻辑,以及过程中碰到了一些疑点的理解过程。

  • 源码分析

    1、注册中的粘性事件分发

    先贴上注册subscribe方法里面的粘性事件代码部分:

    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);
                      }
                  }
              } else {
                  Object stickyEvent = stickyEvents.get(eventType);
                  checkPostStickyEventToSubscription(newSubscription, stickyEvent);
              }
          }
    

    可以看到有两个if判断语句。一个首先判断subscriberMethod.sticky是否为true,也就是该订阅者是否sticky进行了设置。另一个是eventInheritance,这个是创建对象时候设置的属性。OK,继续往下看。

    如果eventInheritance为false的话,直接调用checkPostStickyEventToSubscription方法。如果为true的话,那么遍历eventType以及他的所有子类并调用checkPostStickyEventToSubscription方法。那么先理解下checkPostStickyEventToSubscription方法。

    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }
    

    其实也就是个简单的中转,最终还是调用postToSubscription方法。至于为什么非要搞个方法中转一下,先Mark一下以后再看看。回头再看下subscribe方法中stickyEvents这个Map是从哪里赋值的。贴上代码

    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }
    

    通过postSticky来给stickyEvents填充数据。也就是说当调用postSticky方法后,EventBus就存储了该事件。后续当有人订阅的时候,就会在stickyEvents查询存储到的粘性事件。也就是达到了不会因为时序问题错过了执行该事件的意图。OK!粘性事件介绍到这边,至于postToSubscription()后续的Post方法事件分发会介绍到。

    2、Post方法事件分发
    2.1 理解ThreadLocal

    先贴出post方法的前面一部分代码:

    /** Posts the given event to the event bus. */
    public void post(Object event) {
        //PostingThreadState用于存储各个线程的资源
        PostingThreadState postingState = currentPostingThreadState.get();
        ...
    }
    

    有两个地方需要事先理解:PostingThreadState以及currentPostingThreadState。先看下PostingThreadState这个类:

    /** For ThreadLocal, much faster to set (and get multiple values). */
    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
    

    注释中写道更快的设置值和读取值,不知道为什么会更快Mark一下以后看。看下里面的值,包含了订阅者和事件这两个事件分发的参数以及其他一些用于逻辑处理的参数。

    接下来看一下currentPostingThreadState这个值,他也是在EventBus.java里面定义的。

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
    

    可以看到他是一个ThreadLocal对象,他的初始化就是new出一个PostingThreadState对象。看下ThreadLocal的作用:

    实现一个线程本地的存储,每个线程都有自己的局部变量。所有线程都共享一个ThreadLocal对象,但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。

    那么就是说每个线程只对应一个PostingThreadState数据对象。返回到上面post函数的标注1currentPostingThreadState.get()方法,应该就是获取当前线程对应的数据对象。为了更好的理解(Mark一下以后深入理解),稍微跟下ThreadLocal源码。贴上ThreadLocal的get方法代码:

    public T get() {
        Thread var1 = Thread.currentThread();
        ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);//标注1
        if (var2 != null) {
            ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
            if (var3 != null) {
                Object var4 = var3.value;
                return var4;
            }
        }
    
        return this.setInitialValue();
    }
    

    实际上是通过获取当前线程Thread.currentThread()作为键值,然后看下标注1的getMap方法。

    ThreadLocal.ThreadLocalMap getMap(Thread var1) {
        return var1.threadLocals;
    }
    

    可以看到getMap方法就是获取Thread参数的threadLocals方法。再跟到Thread源码的:

    ThreadLocalMap threadLocals = null;
    ThreadLocalMap inheritableThreadLocals = null;
    

    可以看到Thread定义了一个ThreadLocalMap变量threadLocals。

    综上可得也就是ThreadLocal方法可以获得当前线程持有的ThreadLocalMap对应的数据。返回post代码快可知postingState就是当前线程所持有的数据。

    2.2 遍历并分发事件

    理解了这个,我们再看下post方法中剩下的代码:

        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);//标注1
    	//同一个线程中可以用标志位来判断是否正在发送
        //利用eventQueue考虑的是一种情况。如果post调用过快的时候,可以将event放到eventQueue中排队等待
        if (!postingState.isPosting) {//标注2
            //判断是不是主线程
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    //eventQueue.remove(0)返回的应该是第一个被删除的值
                    postSingleEvent(eventQueue.remove(0), postingState);//标注3
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    

    由于postingState是当前线程才持有的,接下来的逻辑代码都是处理postingState数据,也就不会考虑到多线程问题。

    标注2中postingState.isPosting这个标志位用于判断是否当前线程正在posting,那么有一点很奇怪:如果postingState是单线程持有的。那么在某个线程的代码是一行一行执行的也就是说执行完上个post才执行下个post,那么postingState.isPosting这个标志位不是没什么意义!

    OK,先写个Demo测试一下。在注册之后连续个四个EventBus.getDefault().post(1)过去,贴上日志(标注1下行一打印的Log信息);

    eventName = 1,isPosting = false,TheadID = 1
    eventName = org.greenrobot.eventbus.NoSubscriberEvent@d96e574,isPosting = true,TheadID = 1
    eventName = 1,isPosting = false,TheadID = 1
    eventName = org.greenrobot.eventbus.NoSubscriberEvent@82ffb12,isPosting = true,TheadID = 1
    eventName = 1,isPosting = false,TheadID = 1
    eventName = org.greenrobot.eventbus.NoSubscriberEvent@cd992e3,isPosting = true,TheadID = 1
    eventName = 1,isPosting = false,TheadID = 1
    eventName = org.greenrobot.eventbus.NoSubscriberEvent@1f6e4e0,isPosting = true,TheadID = 1

    可以看到原来是NoSubscriberEvent(没人订阅该事件时候)会使得标志isPosting为true。NoSubscriberEvent是标注2的if代码块中调用post()方法。那么就很好解释了postingState.isPosting标志位的作用。比如说NoSubscriberEvent只是在eventQueue中增加一个Event而已并不想执行标注2的if代码块。接下来看看标注3的代码,它是从eventQueue中取一个event出来,传给postSingleEvent处理。OK!现在post()方法解析完了,现在重点解释下postSingleEvent这个方法,先贴上代码:

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //如果eventInheritance为true,那么遍历所有的父类
        if (eventInheritance) {
            //通过lookupAllEventTypes返回所有eventClass相关的类或接口
            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) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
    

    先看下总的流程:

    (1)如果eventInheritance为true,那么将遍历eventClass继承的所有类以及接口类存入eventTypes中。然后执行postSingleEventForEventType方法,如果eventTypes都没人订阅那么subscriptionFound设置为false。如果eventInheritance为false那么直接执行postSingleEventForEventType方法,如果eventTypes都没人订阅那么subscriptionFound设置为false。

    (2)如果都没人订阅,那么下一步处理各种异常。

    先看下lookupAllEventTypes这个方法以及lookupAllEventTypes相关联的addInterfaces方法:

    /** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
    //将所有的类或者接口存储在eventTypes列表中。
    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;
        }
    }
    
    /** Recurses through super interfaces. */
        //遍历递归所有的父类接口
        static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
            for (Class<?> interfaceClass : interfaces) {
                if (!eventTypes.contains(interfaceClass)) {
                    eventTypes.add(interfaceClass);
                    addInterfaces(eventTypes, interfaceClass.getInterfaces());
                }
            }
        }
    

    这个比较好理解:lookupAllEventTypes方法的while循环里面一直遍历当前类对应的父类以及的接口,而addInterfaces则是递归遍历所有的接口。

    OK,现在回头看下postSingleEvent方法的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;
    }
    

    大概的流程分为两步:第一步,从subscriptionsByEventType中获取subscriptions,subscriptionsByEventType就是注册的时候获取的对象。第二部循环遍历subscriptions并执行postToSubscription方法。

    2.3 根据线程模式执行订阅方法

    OK,那么现在就剩下重点分析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 {
                    //利用Handler来调用到Main线程
                    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);
        }
    }
    

    这个是事件分发的具体实现方法,对应着注解的五个线程模式:

    POSTING:订阅者方法将在发布事件所在的线程中被调用。

    BACKGROUND:如果是主线程则创建一个线程,如果不是主线程那么直接执行方法。

    ASYNC:无论什么情况下都新创建一个线程执行方法。

    至于MAIN以及MAIN_ORDERED,先看下官方的注释:

    On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread. If not on Android, behaves the same as {@link #POSTING}.
    MAIN
    On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},the event will always be queued for delivery. This ensures that the post call is non-blocking.
    MAIN_ORDERED

    大概的意思就是说MAIN_ORDERED优先考虑mainThreadPoster,而该方式对耗时操作作了处理并不会造成阻塞(见后面对mainThreadPoster的分析)。而MAIN则是优先考虑主线程执行,该方式下有可能会阻塞线程。

    这五种线程模式涉及到了三个Poster类。

    第一个先分析BackgroundPoster,实现Runnable, Poster接口。先看下BackgroundPoster中的enqueue方法:

    public void enqueue(Subscription subscription, Object event) {
        //根据传进来的参数Subscription subscription, Object event。获取生成PendingPost变量。
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);//标注1
        synchronized (this) {
            //将获取的pendingPost插入queque队列。
            queue.enqueue(pendingPost);//标注2
            //如果当前没有正在执行的则开启一个线程执行
            if (!executorRunning) {
                executorRunning = true;
                //利用线程池来执行在新线程中运行
                eventBus.getExecutorService().execute(this);
            }
        }
    }
    

    注意到标注1处的obtainPendingPost方法。他是PendingPost类的静态方法,与之对应的还有releasePendingPost方法。那么分析下:

    //在pendingPostPool队列中取一个出来
    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                return pendingPost;
            }
        }
        return new PendingPost(event, subscription);
    }
    
    static void releasePendingPost(PendingPost pendingPost) {
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        synchronized (pendingPostPool) {
            // Don't let the pool grow indefinitely
            //将pendingPost清空后放入pendingPost发送池。如果pendingPost的长度过长,那么不操作。保证长度在10000以内
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }
    

    可以看到obtainPendingPost方法是从pendingPostPool池中取对象,而releasePendingPost方法则是将对象放回池中。releasePendingPost方法则是在反射方法之前会调用,就是说releasePendingPost相当于容器,用完重新放回去。那回过头来介绍下BackgroundPoster的enqueue方法的标注2的enqueue方法,有个queue变量。下面看下queue构建的。

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }
    

    OK,是PendingPostQueue里new出来的。那么看下PendingPostQueue里面怎么实现queue.enqueue的。

    private PendingPost head;
    private PendingPost tail;
    synchronized void enqueue(PendingPost pendingPost) {
        if (pendingPost == null) {
            throw new NullPointerException("null cannot be enqueued");
        }
        if (tail != null) {
            tail.next = pendingPost;
            tail = pendingPost;
        } else if (head == null) {
            head = tail = pendingPost;
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
        //用于触发PendingPost poll中的wait解锁
        notifyAll();
    }
    

    他的功能是往queue链表中增加一个节点,再返回到BackgroundPoster的enqueue方法。可以看到如果当前线程没有在执行,那么请求线程池在分配一个线程执行BackgroundPoster的run方法。那么接下来看下BackgroundPoster的run方法是怎么实现的:

    @Override
    public void run() {
        try {
            try {
                while (true) {
                    //wait(1000)的作用,最大化使用线程资源;防止队列中刚没有任务了就停止线程
                    PendingPost pendingPost = queue.poll(1000);//标注1
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();//标注2
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }
    

    先理解下标注1和标注2这两个比较重要的方法,他们都位于PendingPostQueue中,代码如下所示:

    //用于循环遍历链表
    synchronized PendingPost poll() {
        PendingPost pendingPost = head;
        if (head != null) {
            head = head.next;
            if (head == null) {
                tail = null;
            }
        }
        return pendingPost;
    }
    
    //延时执行POST
    synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
        if (head == null) {
            wait(maxMillisToWait);
        }
        return poll();
    }
    

    可以看到poll()方法就是从链表中取出一个数值出来,而poll(int maxMillisToWait)是延时了maxMillisToWait毫秒。回头看下该类中的enqueue方法中有个notifyAll(),也就是当有新数据加入链表的时候就不用等maxMillisToWait毫秒直接调用poll取数据。综合理解BackgroundPoster的run方法,queue.poll(1000)用于延时等待新的poster,一有新的节点马上执行poll()然后反射执行方法。综上所述,BackgroundPoster的enqueue方法将poster加入链表,run方法在链表中取出链表并分发出去。

    AsyncPoster

    和BackgroundPoster方法类似,先看下enqueue方法代码:

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }
    

    对比BackgroundPoster发现少了个executorRunning的判断。也就是说每调用一次enqueue,就创建一个线程运行。而BackgroundPoster则是每个时刻只有一个线程在运行。

    HandlerPoster

    他和前两者的差别是调用了系统的Handler而不用自定实现runnable接口,enqueue代码如下所示:

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }
    

    可以看到用Handler的sendMessage取代了线程池。OK,下面看看handleMessage方法:

    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {
                PendingPost pendingPost = queue.poll();//标注1
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;//标注2
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
                //当maxMillisInsideHandleMessage的时间内没有清空,按么重新sendMessage
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {//标注3
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
    

    标注1可以看到不用像BackgroundPoster一样,等待1秒钟。因为不像BackgroundPoster的线程执行完就结束,主线程始终是在线的。

    标注2处可以看到,当链表中所有值都取出来之后handler的激活状态标志handlerActive切成false。

    标注3处可以看到,由于是在主线程处理逻辑。所以超过一段时间重新sendMessage一次避免阻塞UI线程。

    可以看到主要区别在于HandlerPoster使用了系统的Handler所以不用考虑线程结束。但是系统的Handler又容易阻塞所以对这个问题做了处理。

  • 结语

    1、Handler的机制,这个也是后续要理解的一个专题。

    2、还有很多细节点知其然不知其所以然,需要后续继续理解吃透。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值