EventBus 事件线程切换原理

今天学习了一下EventBus 的源码,将其在接收事件时指定线程执行的大致原理梳理了一下,提炼整理,作为学习总结,也方便日后查阅。

正文

EventBus 可以在不同的线程发送事件、并在指定类型的线程接收和处理事件。在不同线程情况下,发送、接收并处理事件大致有4种情况:
A:在主线程发送,在子线程发送;B:在主线程执行订阅方法,在子线程执行订阅方法;AB两两搭配会有四种组合,也就是在不同线程情况下,发送、接收并处理事件的4种情况。
主要的问题其实只有两个,其一:如何判断当前发送事件的线程是否是主线程;其二:如何在接收事件时指定线程并执行;
一个一个来看。

1.如何判断是否在主线程发送

EventBus在初始化的时候会初始化一个MainThreadSupport对象,它会去获取主线程的Looper对象并存起来。(当前最新版本如果不是Android环境MainThreadSupport会为空,非Android环境也就无需关注是否是UI主线程的问题了)
在发送消息的时候,EventBus会取出当前线程的Looper对象对象与主线程Looper对象做比较,如果相同,说明是在主线程发送消息,如果不同,说明是在子线程发送消息。以下是MainThreadSupport的代码:

public interface MainThreadSupport {

    boolean isMainThread();

    Poster createPoster(EventBus eventBus);

    class AndroidHandlerMainThreadSupport implements MainThreadSupport {

        private final Looper looper;

        public AndroidHandlerMainThreadSupport(Looper looper) {
            this.looper = looper;
        }

        @Override
        public boolean isMainThread() {
            return looper == Looper.myLooper();
        }

        @Override
        public Poster createPoster(EventBus eventBus) {
            return new HandlerPoster(eventBus, looper, 10);
        }
    }
}
2.怎么在指定的线程执行订阅者的方法

在找到订阅者之后,判断不同线程情况下执行订阅方法的逻辑基本都在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 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);
        }
    }

以上代码中,如何判断是否是主线程上面已经说过了。
invokeSubscriber()这个方法其实就是拿到订阅者的信息,直接执行订阅方法了(通过反射获取)。
subscription对象中有一个SubscriberMethod对象,而SubscriberMethod这个对象基本上包含了订阅者的执行线程、订阅方法、是否粘性事件、优先级等等信息。如下:

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;
    //省略若干代码...
}

所以,从postToSubscription()方法可以看出,当threadMode是POSTING时,直接在当前线程执行,不做判断,也就是从哪个线程发送,就从哪个线程执行订阅方法;
我们这里主要来看threadMode为MAIN和BACKGROUND的情况:

在主线程执行

当threadMode为MAIN时,如果在主线程发送,直接在当前线程执行,没有问题。如果不在主线程发送,会有一个mainThreadPoster将包含订阅者信息的对象加入队列。这个mainThreadPoster其实是Handler的子类,它利用Handler的消息机制,发送消息并在主线程接收消息,获取到订阅者的信息后在主线程处理事件,从而实现在子线程发送消息,在主线程处理事件。
这里直接利用Hadler的现成机制,可谓简明高效。

在子线程执行

当threadMode为BACKGROUND时,如果不在主线程发送,直接执行,没有问题。如果在主线程发送,这里有一个backgroundPoster将包含订阅者信息的对象加入队列。BackgroundPoster其实是Runnable的子类,在自己的run方法中不断从队列中取出订阅者对象,执行订阅方法。EventBus维护了一个线程池,BackgroundPoster会将自己丢到线程池中,执行自己的run方法,从而实现在在主线程发送事件,在子线程中执行订阅方法。

以上,就是EventBus切换执行线程的主要流程。
其实并不难。
末尾附上一篇讲解EventBus原理的简练博文:戳这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,209评论 0 6
  • EventBus地址:https://github.com/greenrobot/EventBus 一、event...
    君莫看阅读 2,004评论 2 11
  • 先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就...
    Ten_Minutes阅读 573评论 0 2
  • 刚刚在“成长汇”做婷婷的开场介绍, 看见念头: 就文字吧,文字介绍婷婷可以的。 没问题。 问自己这是你想要的吗? ...
    尚灵心阅读 446评论 1 5
  • 道理都是讲给别人的,而你是用来爱的。
    失宠大鸡排阅读 143评论 0 0