源码解析--理解EventBus工作流程

EventBus源码解析

EventBus是我们使用相当频繁的一个开源库,使用方式比较简单,功能强大,如此强大的一个库,怎能不一探其内部实现?
##EventBus的创建和变量
###创建EventBus对象

我们在使用EventBus的时候,往往都是EventBus.getDefault(),使用静态方法getDefault()一个对象,然后再进行register、post等操作。EventBus类的构造方法对外只有一个无参构造,而其内部则是调用了EventBus(EventBusBuilder builder)方法。EventBus对象的获取其实有四种方法:

  • **EventBus.getDefault():**最常使用的方式,单例模式,使用默认的配置。app中全部使用该方式调用,可保证获取对象唯一,不会导致订阅事件和发送事件不是同一个EventBus对象的事情发送。
  • new EventBus(): 比较少用,同样是使用默认的配置
  • EventBus.builder().build(): 通过Builder方式构建一个对象,可理解为构建者模式,可以手动进行各项配置(日志打印、发送异常事件、抛出异常等设置)
  • **EventBus.builder().installDefaultEventBus():**同样为Builder方式构建的对象,可以手动配置,和build()不同的是它构建出来的是EventBus的默认对象,也就是和EventBus.getDefault()获取的是同一个对象。注意:installDefaultEventBus()方法需要在EventBus.getDefault()之前调用,否则对象已经创建,配置无效,抛出异常。

EventBus成员变量

介绍几个关键变量:

  • 默认的EventBuilder对象,默认配置
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
  • 关键变量,根据订阅事件类型区分的所有订阅事件对象集合(包含订阅者和订阅方法),可查找出订阅某一个事件的所有方法
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
  • 关键变量,订阅者和它对应的所有订阅事件的集合,可以根据订阅者查找出该订阅者订阅的所有事件
private final Map<Object, List<Class<?>>> typesBySubscriber
  • 比较重要的一个变量,PostingThreadState类是描述当前post的状态。所有通过ost方法发送的事件会先存储到该类里面的队列中,然后再循环从队列中取出进行分发操作。另外,该类包含是否正在分发、post操作是否在主线程、操作是否取消等变量。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
  • 分别控制在主线程的分发、后台线程分发、异步线程分发操作
private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
  • 通过该对象查找某一个订阅者中所有的订阅方法
private final SubscriberMethodFinder subscriberMethodFinder;

EventBus的订阅

Register()

订阅,即register()方法,我们知道一个类中调用EventBus.getDefault().register(this)后,在该类中就能收到注册的订阅事件。

在此之前,我们先了解两个类:

**SubscriberMethod:**订阅方法类,也就是我们在代码中,使用@Subscribe注解的方法,用于监听和接收处理事件的方法。

final Method method;         //方法名、参数等信息,android原生反射类
final ThreadMode threadMode; //在哪一个线程中接收此事件
final Class<?> eventType;    //事件类型,如String.class等
final int priority;          //优先级,
final boolean sticky;        //是否粘性传输
String methodString;         //用来检测是否是同一个监听方法

**Subscription:**订阅者和里面的一个事件方法 对应的对象

final Object subscriber;                //订阅者(例如某一个Activity对象)
final SubscriberMethod subscriberMethod;//订阅者内的一个订阅方法

继续走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);
            }
        }
    }

可以看出这个方法中并没有写很多东西,首先是通过之前提到的subscriberMethodFinder找出所有订阅的方法,具体实现是通过反射的方式查找出来。继而对这些方法分别进行注册、绑定等操作,所以继续看subscribe()方法。

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	    //获取该订阅方法的事件类型
        Class<?> eventType = subscriberMethod.eventType;
        //创建一个新的订阅者和订阅方法组合对象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //根据事件类型,获取当前所有监听该类型的订阅操作(订阅者和订阅方法)
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        //如果找不到,新建列表,以当前事件类型为key塞入map集合中
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //同一个订阅者对象中,如果有该订阅方法,不能重复订阅,也就是一个类中的onEvent方法不能是相同的参数(事件类型)
            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) {
            ...
        }
    }

这个方法就是整个register流程的关键了,但简单来说可以概括为两点:
1、获取所有注册了这个事件类型的列表,将新找出来的方法插入列表,最后以事类型为key,列表为value更新或插入到map集合中。
2、获取这个订阅者所有相关的订阅操作列表,订阅者为key,列表为value,塞入到map集合中。

unRegister()

一般会在onDestroy方法中取消注册,取消注册后不再监听接收消息。从register流程可知,在unRegister中只需反着来就行,怎么反?移除,移除,移除!

    public synchronized void unregister(Object subscriber) {
        //找出这个订阅者订阅的所有事件
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
	        //遍历这些事件方法,在事件类型-订阅操作map集合中找出这些方法并移除
            for (Class<?> eventType : subscribedTypes) {
	            //方法实现为:找出这个事件类型的所有订阅操作,循环匹配如果是当前这个订阅者订阅的,从列表中移除
                unsubscribeByEventType(subscriber, eventType);
            }
            //最后从map集合中移除这个订阅者
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

##事件监听

Subscribe注解

在使用@Subscribe注解时,有时会加上threadMode = ThreadMode.MAIN这样的代码,这是干嘛用的呢?设置监听接收该事件所在的线程!

不仅如此,我们还可以设置是否粘性事件、设置优先级。Subscribe注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
	//可设置线程模式,默认为POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
	//是否粘性传输,默认false
    boolean sticky() default false;
	//优先级,默认为0,排到队尾
    int priority() default 0;
}

ThreadMode

这个是EventBus中比较牛批的一个设定了,可以让你指定在何线程中执行操作。

####ThreadMode.POSTING
默认方式。事件将在post后直接分发给订阅者,与发送事件在同个线程中执行,由于避免线程切换,开销也是最小的。这也是官方推荐的模式,如果你的项目中并不涉及太多的线程之间的切换。但是尽量不要在方法中执行耗时操作,避免发送线程为主线程时被阻塞

####ThreadMode.MAIN
事件的监听和执行将在主(UI)线程中进行。如果发送事件所在为主线程,则直接执行,否则通过主线程Poster统一排队分发执行。同样不要在方法中执行耗时操作,避免阻塞主线程
####ThreadMode.MAIN_ORDERED
基本和MAIN一致,不同的是:不管发送事件所在线程为何线程,都要通过主线程Poster统一排队分发执行。、
####ThreadMode.BACKGROUND
后台线程执行。如果发送事件不在主线程,将直接在当前线程执行;如果发送事件所在为主线程,EventBus将会开启一个唯一的后台线程,所有事件在此线程中排队分发执行。同样最好不要在方法中执行耗时操作。
####ThreadMode.ASYNC
异步线程中执行。不管发送事件线程为何线程,都不会在此线程中执行,新开一个异步线程执行操作,一般需要在监听中执行耗时操作时会使用此模式。EventBus会使用线程池来管理从而避免开启过多的线程。

发送事件

这个是比较关键的了,使用时只需要简单的post一下,那么各个订阅者如何收到这些事件呢?而且还是想在哪个线程就在哪个线程?同样,我们先来了解几个类。

PendingPost
这个类是用来描述一个准备发送的事件,内部维护一个队列用于复用。
获取一个对象,利用列表进行复用:

    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        /**
         * 如果当前正在从队列中复用,则直接return一个new出来的对象
         * 如果队列为空,同样返回新对象
         * 如果能够复用,则取队列最后一个对象,将其变量置为传入的参数,最后return这个对象
         */
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                //取对象使用的是remove方法,从队列中移出,这样保证队列不会越来越长
                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
            /**
             * 当前队列不能超过10000,因为每一次obtain要么获取一个新对象,要么从队列中移除最后一个对象,
             * 所以即是保证当前同一个线程同一时间内需要发送的事件操作不超过10000
             */
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }

也就是说,我们每post发送一个事件,最终都会创建成这样一个对象准备发送,里面包含了发送的事件对象,事件类型,订阅者信息,订阅方法这些信息。

PendingPostQueue
这个类可以看成是一个链表,每一个节点就是一个PendingPost对象,通过PendingPost的next变量连接,取值、移除可参照单链表用法。

###开始Post
1、主动调用post()方法发送事件,传入一个Object对象
2、获取我们开始提到的PostingThreadState 对象,将该事件添加到里面的列表,判断当前状态。

    public void post(Object event) {
        /**
         * 获取当前发送对象,添加到队列中,如果正在发送其他事件,则在发送后会继续发送新加入的事件(while循环)
         * 如果当前空闲,则立即发送事件
         * 最后重置状态
         */
        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 {
                //从队列中获取事件对象使用remove方法获取,省去发送后从队列删除这一操作
                //列表为空时退出循环,因为前面的逻辑,是有可能正在post操作的时候,新的post发过来了,添加到了列表中,
                //但是还没有没有真正发送,所以发送完一个之后需要判断是否还有需要发送的事件
                while (!eventQueue.isEmpty()) {
		            //真正发送在这里
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

3、根据事件类型,在集合中查找是否有订阅这个事件类型的订阅方法,如果没有找到,抛出异常;如果找到,循环处理每一个订阅操作

            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;
                }
            }

5、最后的发送,根据订阅方法选择的线程,选择相应的poster分发

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        //当初注册@Subscribe注解时设置的在哪个线程中接收事件,默认Posting,即当前线程(和发送者同一线程)
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
	            //直接当前线程执行
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
	            //如果当前是主线程,直接执行
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                //否则使用主线程Poster统一排队分发
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
	            //不管处于何线程,统一使用主线程Poster排队分发
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
	            //如果不在主线程,直接执行,否则,使用后台Poster排队分发
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
	            //不管处于何线程,统一使用异步Poster排队分发
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

执行

执行比较简单了,使用java反射机制

    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);
        }
    }

几个Poster

在哪一个线程中接收到事件,取决于用哪一个Poster发送,默认的话是直接执行,主线程执行是使用HandlerPoster,后台执行是使用BackgroundPoster,异步执行使用AsyncPoster。
首先来看Poster,公共实现的一个借口类:

interface Poster {
    void enqueue(Subscription subscription, Object event);
}

里面就一个执行方法,也就是发送事件

AsyncPoster

异步Poster实现了Runnable和Poster接口,利用Runnable新开一个线程执行。
执行enqueue方法时,内部执行了Runnable的必须实现方法run(),这个就是新开一个线程执行啦,线程里面调用EventBus里的**invokeSubscriber()**反射方法。

BackgroundPoster
与AsyncPoster不同的是,它只开了一个线程,当有事件进来需要分发时,先判断当前是否正在执行其他操作,如果空闲立即执行,否则先插入列表进行排队,待前一个事件处理完成之后再下发下一个事件。

HandlerPoster
最后是主线程Poster,这个Poster在创建时,传入的是Looper.getMainLooper(),所以实现了在主线程监听。这个Poster使用Handler模式,收到发送事情消息请求时,调用自身sendMessage()方法,然后再handleMessage中处理请求。

    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            //开始处理时间
            long started = SystemClock.uptimeMillis();
            while (true) {
	            //从队列中取出一个PendingPost操作
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //最终的使用反射执行订阅方法
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                //如果循环执行时间大于10毫秒,退出循环,避免一直从队列中取数据,
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }

End

至此,EventBus整个流程基本结束。本文从流程着手分析,基本上解释了整个EventBus的框架,内部实现原理,此中许多不足之处,请各位不吝赐教。
感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值