EventBus源码解析,看这篇就够了

先说下大家熟悉的EventBus

EventBus使用起来特别方便简单,如果一个类需要被通知,然后做一些事情,那么只需要在这个类里面调用EventBus.getDefault().register(this);方法进行注册,其次是使用@Subscribe注解方法,那么只需要在需要发送事件的地方,调用post或者postSticky方法(参数就是被注解函数的参数类型)就可以在这个被注解的方法这里收到事件发送。如果不再需要接收事件了,那么调用unregister方法解除当前类注册就好了。其中的postSticky方法是粘性注册,使用场景好比就是我们在一个Activity还没开启时候就向这个Activity发送事件,然后等进入这个Activity之后再响应之前发送的事件,而post使用场景是正常的先注册后发送事件了。

分析EventBus源码该如何进行?

还是从经常使用的方法入手,从其使用流程入手,在使用时候应该也会有这样的疑问,为什么register之后,@Subscribe注解的方法能够收到从别的任何地方任何线程post过来的事件呢?更神奇的是为什么postSticky粘性发送可以在类还没创建的时候就发送消息,类创建了才接收到呢?另外我要是接收者和发送者在不同线程怎么办?我要是不需要接受消息,或者页面退出时候我不调用unregister方法好像也没报错,是不是这个操作就 OK 呢?更重要的是register、@Subscribe、post、postSticky以及unregister这些我们常用的方法,他们背后到底做了什么呢?

首先分析EventBus.getDefault()方法

之所以先从它入手,是因为无论是register还是post、postSticky以及unregister等方法的调用之前都先调用了getDefault方法,所以必须先从它入手,来看看这个getDefault是做了什么。

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

其实这个getDefault就是获取了一个单例对象,也是使用了双重判空的方式来获取单例。既然是获取对象,那么肯定需要实例化,那么自然也需要去看看它调用的无参构造方法了:

public EventBus() {
        this(DEFAULT_BUILDER);
}

这个无参又调用了有参构造方法

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();//获取主线程支持类
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
}

我们看到了有参的构造函数的参数EventBusBuilder,只是这里直接用了DEFAULT_BUILDER,这是个已经实例化好的EventBusBuilder,那就先看看 EventBusBuilder ?其实这个EventBusBuilder就是来给EventBus中的大多属性来进行赋值的,之所以这么做,是为了避免EventBus的构造方法参数太长,而EventBusBuilder中需要着重看的一个方法是:

//获取一个主线程的支持类,光是这个方法就牵涉到了好多类啊,大意就是定义了订阅存储类,事件的发送以及接收,这是通过handler来实现的
MainThreadSupport getMainThreadSupport() {
        if (mainThreadSupport != null) {
            return mainThreadSupport;
        } else if (AndroidLogger.isAndroidLogAvailable()) {
            Object looperOrNull = getAndroidMainLooperOrNull();
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {
            return null;
        }
}
public interface MainThreadSupport {

    //判断是否是主线程的,在android中就是ui线程
    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);
        }
    }

}

这个方法里面的MainThreadSupport是个接口,这个接口主要是给Android用的,这个接口里面的isMainThread可以用来判断当前线程是否是主线程,里面的createPoster创建一个在ui线程中调用发送事件的对象,创建的就是HandlerPoster这个对象。

这个HandlerPoster是干什么的呢?

开头咱们说过,在任何线程中发送过来的事件,都可以被处于ui线程的类的被注解的方法接收到,这是如何做到的?

就是通过这个HandlerPoster实现的,顾名思义这个跟Handler有关,而Handler咱们再熟悉不过了,记得最初写网络请求的时候,都是new一个Thread在里面发送网络请求,然后把网络请求结果通过handler发送到ui线程刷新ui,这里的转换线程也是这样子的,如果要在子线程中回调ui线程中的方法,就是通过handler进行转换的。

到这里才把构造函数的参数看的差不多,而构造函数体里面则是通过EventBusBuilder对EventBus的属性做一些初始化,我们上面说了有HandlerPoster做从子线程到ui线程的切换。EventBus构造函数体中的BackgroundPoster和AsyncPoster又是干啥的呢?

到这里为了更好的理解这俩是干啥用的,就必须得先看看Subscribe这个注解的threadMode属性对应的ThreadMode类了,这个类是用来指定在哪种线程模式下回调被注解的方法。下面我们逐一来看各种模式

threadMode = POSTING
这种模式的意思就是事件发送者在哪个线程,那么就在哪个线程回调@Subscribe所注解的方法,同时这是默认也是官方推荐的模式,因为这种模式避免了线程切换,但是如果恰好在ui线程中发送事件,但是又在回调方法中做了耗时操作,那么就会阻塞ui线程,所以在这种情况下用这种模式就不太适合了。

threadMode = MAIN
这种模式的意思是无论事件发送者在那种线程,都统一在ui线程中回调,但是我们知道发送者可能不在ui线程,所以用到了上面的handlerPost切换线程呀,但是如果发送方就是在ui线程中,还是要避免阻塞ui线程,但是切换线程我们知道是通过handler来进行的,所以不存在阻塞的问题。

threadMode = MAIN_ORDERED
这种模式也是保证在ui线程中回调,但是它的特殊之处在于永远都通过handlerpost来切换线程,通过消息队列来进行,无论发送者在哪个线程,所以肯定能够避免ui线程阻塞。

threadMode = BACKGROUND
这种模式表示的保证在子线程中回调被注解的方法,同样如果事件发送者就在子线程,那么直接回调了,如果不在的话,就会利用BackgroundPoster来切换线程,BackgroundPoster其实就是一个runnable,通过一个线程池来执行这条runnable,而被回调方法就是在runnable的run方法里面,可不就是在子线程中么?

threadMode = ASYNC
这种模式就比较“奇葩”了,因为它代表回调的线程跟事件发送的线程总是不在一个线程。这种模式下永远会在一条新的runnable里面执行方法回调,所以可不就是永远新起线程么?但是想想这种模式的使用场景真的少呀。它是通过AsyncPoster来实现的,那BackgroundPoster和AsyncPoster有啥区别呢?看源码发现BackgroundPoster每次只会执行一个回调,因为加了锁,所以不会并发,但是AsyncPoster就不一样了,它是会并发执行的。

register方法做了什么

我们也知道subscribe所做的事情就是记录下来当前的注册类,以及被@subscribe注解的方法,为的是将来post时候能够收到消息?可是这个记录是如何记录的呢?它是如何做到能够将来收到post的事件的呢?接下来我们来看register方法做了什么吧。

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();//获取订阅的当前类
        //获取到当前订阅类中被@Subscribe注解的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
}

参数是当前类对象,一般是当前类,如上的findSubscriberMethods方法就是在寻找所有被@Subscribe注解的方法,我们可以先想想,如果是我们我们会如何获取呢?给我一个Class,那么最先想到的肯定是通过反射获取到所有的方法,然后再看看哪个方法被@Subscribe注解了,但是再想一步如果传进来类已经获取过了,那么还需要在获取一次吗?答案不是必须的,毕竟通过反射还是挺耗费性能的,所以在首次获取之后可以缓存下来,其实看了代码,eventbus也大致是这么做的,但是eventbus不只是寻找传入类的被注解的方法,还会寻找此类的父类以及此类所实现接口里面的被注解的方法,所以这一点咱们在使用的过程中也要多多注意,不然可能会出现不该接收的接收到事件发送的情况了,当然了我们知道所有的类最后都会继承一些系统类,所以系统类是会被排除在外的,因为系统类肯定是不可能接收到事件发送的。

关于寻找被注解方法这里还有个点需要注意,虽然说我们找的是方法,但是会包含这么几个元素,方法名称,方法的参数(也就是发送的事件类型),以及注解里面的信息,就是是否是粘性注册、优先级以及方法回调的线程,寻找该类对应的注解方法时候这几个因素都要考虑,特别是方法参数即事件类型,会有一个事件类型和方法的对应map,我们知道一个事件类型可能会对应多个方法,一般是不同类之间的,因为同一个类没有必要写两个参数相同的但是方法名不同的方法,所以这里会有个校验,一个事件类型可以对应多个方法,但是必须在不同的类,如果在同一个类里面,那么eventbus是会抛出IllegalStateException()异常的,eventbus在这方面做了比较严格的校验。

现在我们获取了一个类以及其父类和此类所实现接口中所有被注解的方法,理论上是要有一个类和所有注解方法的对应关系,但是我们想我们在发送事件的时候,是直接发送的与被注解方法参数类型匹配的类对象,所以首要的对应关系不就是所谓的事件类型和对应的有此事件类型的方法的集合的对应关系吗?毕竟一个事件发送,可能会有不止一个方法收到,所以可以看到eventbus里面有个名为subscriptionsByEventType的map,它存储的就是我们刚刚说的那种对应关系,key就是这个事件类型。但是仅仅有这个方法的话还是不ok的,毕竟我们还是要知道所谓的方法在哪个类里面的,不然是没法进行回调的,到后面我们知道,所谓的post就是在进行方法的回调,我们上面说了根据事件类型获取了对应的注解方法,那么是不是也可以根据注册类对应到事件类型呢?事实上,eventbus就是酱紫做的,就是有一个名为typesBySubscriber的map来维持这种对应关系,key就是注册类,value是事件类型的集合,毕竟一个类中可以有不同事件类型的注解方法。

我们知道我们在写注册方法的时候是有优先级的,虽然这个可能用的不是那么多,优先级高的可以先收到发送的事件,但是大家想过这个是如何实现的吗?关键点不在于事件发送方,看了源码知道在于存储这种对应关系的时候,我们看subscribe中的下面这个循环:

int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {//遍历这个eventType对应的list
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
}

主要是那个if里面,如果subscriptions为空,那么肯定是i==size时候被添加入集合,如果subscriptions已经有了元素,那么会找到第一个比新subscribe方法的优先级低的然后,添加到i这个位置,i位置的元素后移,然后跳出循环。那我们想在发送事件,回调方法的时候肯定也会从这个集合里面从头开始循环,那么添加时候就是越往前优先级越高,那么回调时候,自然是优先级高的先被取到,然后回调了,所以优先级的实现逻辑就在这里。

这里引出另外一个问题,就是eventbus的粘性注册到底是怎么实现,先发送,后注册还能收到的呢?常规思路肯定是postSticky时候先存起来,然后到了事件接收类,再拿出来执行回调了,发送的我们先不管,回调实质上就是在register时候做的,也就是在上面我们说的那个subscribe里面做的,subscribe不仅仅做了我们上面讲的对应关系,还做了粘性注册事件的检测以及回调。

先简单看一下postSticky吧:

/**
     * <p>
     * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
     * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
     * <p>
     */
    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);
}

看到了最后还是调用了post,但是在之前也是将事件类型class以及事件类型对象存储在一个map中,所以subscribe时候也必然是会从stickyEvents中取数的呀,就是根据事件类型,取出事件类型实体,然后就是进行事件回调了,那就姑且来看看是怎么回到的吧?

//调用注册的方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {//判断注册模式,一般使用的都是ui线程呢
            case POSTING://直接在当前线程回调
                invokeSubscriber(subscription, event);
                break;
            case MAIN://在ui线程回调
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {//如果当前线程不是ui线程,那么就会加入队列
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED://在ui线程异步回调,不会阻塞线程
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber 对呀,如果在子线程中怎么办呢?直接回调不久出错了么
                    //当前线程可能不在ui线程,直接回调就会可能导致在非ui线程刷新ui
                    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);
        }
}

大家看到这个方法就能知道咱们当初设置的threadMode起作用了,就是确定要回调的线程,结合咱们上面讲threadmode时候说的,这里代码大家应该都看的明白,就不赘述了
 

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            //subscription可以认为是注册的类,接受发送事件的类。event则是注册方法的参数类型
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
}

其中每种模式最终都会调用这个方法,我们可以看大就是通过反射调用的,也没啥说的。关于注册部分以上就是全部的内容,还是挺多的。接下来我们来看事件发送吧,postSticky咱们上面说过了,直接看post就好了。其实post根据传进来的事件类型对象,从subscriptionsByEventType中取出eventType对应的注解方法list,然后循环这个list最终还是会调用到了postToSubscription方法了,到这个方法就跟上面的连起来了。

还有最后一个问题就是我们如果在页面销毁时候不调用unregister会怎样,试过的话其实也没啥,只是每次post时候,依然会去回调,但是我们知道被回调方法已经不存在了,那么必然会报错呀,但是eventbus已经考虑到了这种情况,try catch了,但是在不用的时候最好还是unregister吧,毕竟trycatch还是挺耗费资源的。

以上就是本次全部内容,有问题留言沟通,或者关注我的公众号「he_xiao_pang」与我交流。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值