EventBus源码分析

来源:互联网 发布:策略模式 java的概念 编辑:程序博客网 时间:2024/05/21 17:42

以下图表来自:codeKK
EventBus GitHub项目源码传送门:https://github.com/greenrobot/EventBus

功能介绍

EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件。事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等。
传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。


概念梳理

这里写图片描述

1、Event:事件,相当于我们使用Handler时的Message,一般我们自定义一个类来包装,这个类就是我们下面要提到的事件类型(EventType)。例如:

class UIEvent{  int id;  Object data;}

事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个 Sticky 事件。

2、Subscriber:订阅某种事件类型的对象,我们使用EventBus时,要在我们的类中注册对象:

EventBus.getDefault().register(this);

也要记得在合适时机反注册:

EventBus.getDefault().unregister(this);

注册EventBus以后,该类成为一个订阅者,当有发布者(Publisher)发布这种类型的事件时,EventBus会按一定规则执行订阅者的onEvent函数(事件响应函数)。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。

这里的onEvent响应函数包括:

onEvent(Object obj){};//在Publisher所在线程执行响应函数onEventMainThread(Object obj){};//在主线程执行onEventBackgroundThread(Object obj){};//在唯一后台排队执行onEventAsync(Object obj){};//另开线程异步执行,适用于长耗时操作

我们可以根据具体需求,选择合适的响应函数,这里需要注意:

A、响应函数名字必须是上面四个中的一个。
B、响应函数只能有一个参数,也将是我们以后post的对象。

我们将在下面的findSubscriberMethods源码解析中详细解释原因。

3、Publisher:发布某种类型事件的对象。例如:

EventBus.Post(new UIEvent(1, "gk"));

我们不关心发布者是谁,我们只关心发布的事件。这里我发布了一个事件类型为UIEvent的事件,那么所有注册EventBus的Subscriber都会收到该事件,然后根据事件类型UIEvent,筛选出需要执行的onEvent响应函数。

如果我们需要指定某个对象执行响应函数,那就需要在Event对象中加入唯一标识,比如我这里的id。


使用流程

先贴上EventBus的类设计图:

这里写图片描述

接着我主要从register和post两方面谈谈整个流程。


注册

这里写图片描述

源码如下:

 private void register(Object subscriber, String methodName, boolean sticky) {     List subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),        methodName);     for (SubscriberMethod subscriberMethod : subscriberMethods)       subscribe(subscriber, subscriberMethod, sticky);   }

整个过程简述为:SubscriberMethodFinder对象用findSubscriberMethods方法根据注册者和响应方法名找到注册者的所有subscriberMethod信息,如果没有,则添加并缓存。然后把这些subscriberMethod信息跟注册者一一建立关联。

值得一提的是,register的第二个参数methodName默认是:onEvent
也就是说我们的响应函数要以onEvent开头

接下来我们看看分解操作:


我们先看看subscriberMethod都包含哪些信息:

 final class SubscriberMethod {   final Method method;   final ThreadMode threadMode;   final Class<?> eventType;   String methodString;   ……  }

method:响应函数的名字,即我们上面提到的四种响应函数。
threadMode:线程类型,我们看看它长什么样:

public enum ThreadMode{  PostThread,   MainThread,   BackgroundThread,   Async;}

细心的同学会发现,它跟我们的响应函数长得极像且一一对应。的确是这样,我们在注册以后,EventBus会根据我们实现的响应函数名称,自动选择对应的线程类型。

eventType:事件类型,即最上面楼主举例的UIEvent。

methodString:响应函数名字 +事件类型名构建的字符串,用于比较两个subscriberMethod对象是否相等。

小结:

我们给一个对象注册EventBus后,SubscriberMethodFinder对象会根据我们的注册者,找到:

1、响应函数
2、执行响应函数所在的线程
3、响应事件类型


然后我们看看SubscriberMethodFinder中的findSubscriberMethods方法,回顾上面所说,这个方法会返回注册者的所有SubscriberMethod 信息。

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {        String key = subscriberClass.getName() + '.' + eventMethodName;        List subscriberMethods;        //优先读缓存,如果有缓存纪录,直接返回        synchronized (methodCache) {            subscriberMethods = (List) methodCache.get(key);        }        if (subscriberMethods != null) {            return subscriberMethods;        }        //如果缓存中没有,则遍历注册者的每个函数并递归查找父类        List subscriberMethods = new ArrayList();        Class clazz = subscriberClass;        HashSet eventTypesFound = new HashSet();        StringBuilder methodKeyBuilder = new StringBuilder();        while (clazz != null) {            //遍历前提条件:注册者不能是以java. javax. android. 开头的类            String name = clazz.getName();            if ((name.startsWith("java.")) || (name.startsWith("javax.")) || (name.startsWith("android."))) {                break;            }            //开始遍历注册者每个方法            Method[] methods = clazz.getDeclaredMethods();            for (Method method : methods) {                String methodName = method.getName();                //遍历规则1:方法是以"onEvent"开头                if (methodName.startsWith(eventMethodName)) {                    Class[] parameterTypes = method.getParameterTypes();                    //遍历规则2:方法的参数只有1个                    if (parameterTypes.length == 1) {                        String modifierString = methodName.substring(eventMethodName.length());                        ThreadMode threadMode;                        //如果方法名为"onEvent",线程模式(threadMode)为PostThread,即发布者所在线程                        if (modifierString.length() == 0) {                            threadMode = ThreadMode.PostThread;                        } else {                            ThreadMode threadMode;                            //如果方法名为"onEventMainThread",线程模式(threadMode)为MainThread,即主线程                            if (modifierString.equals("MainThread")) {                                threadMode = ThreadMode.MainThread;                            } else {                                ThreadMode threadMode;                                // 如果方法名为"onEventBackgroundThread",                                // 线程模式(threadMode)BackgroundThread,即后台程                                if (modifierString.equals("BackgroundThread")) {                                    threadMode = ThreadMode.BackgroundThread;                                } else {                                    ThreadMode threadMode;                                    // 如果方法名为"onEventAsync",线程模式(threadMode)Async,即异步                                    if (modifierString.equals("Async")) {                                        threadMode = ThreadMode.Async;                                    } else {                                        //如果方法是以onEvent开头的其他方法,注册者又不在忽略名单里,则抛出异常                                        //至此,就很明白了,为何注册者内部的响应函数必须是上面说过的四个中的一个                                        if (skipMethodNameVerificationForClasses.containsKey(clazz)) {                                            continue;                                        }                                        throw new EventBusException("Illegal onEvent method, check for typos: " + method);                                    }                                }                            }                        }                        ThreadMode threadMode;                        Class eventType = parameterTypes[0];                        methodKeyBuilder.setLength(0);                        methodKeyBuilder.append(methodName);                        methodKeyBuilder.append('>').append(eventType.getName());                        String methodKey = methodKeyBuilder.toString();                        //把响应函数名、线程模式、参数类型一起构造成一个SubscriberMethod对象,保存                        if (eventTypesFound.add(methodKey)) {                            subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));                        }                    }                }            }            //遍历完注册者以后,再遍历其父类,规则同上            clazz = clazz.getSuperclass();        }        if (subscriberMethods.isEmpty()) {            throw new EventBusException("Subscriber " + subscriberClass + " has no methods called " + eventMethodName);        }        //最后把subscriberMethods缓存        synchronized (methodCache) {            methodCache.put(key, subscriberMethods);        }        return subscriberMethods;    }

至此,我们获得了注册者所有的subscriberMethod信息,下面就要开始真正的注册了。再次回顾下源码:

 private void register(Object subscriber, String methodName, boolean sticky) {     List subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),        methodName);     for (SubscriberMethod subscriberMethod : subscriberMethods)       subscribe(subscriber, subscriberMethod, sticky);   }

现在我们进入 subscribe(subscriber, subscriberMethod, sticky)方法看一看:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky) {        this.subscribed = true;        Class eventType = subscriberMethod.eventType;        //1、通过subscriptionsByEventType得到该事件类型所有订阅者        CopyOnWriteArrayList subscriptions = (CopyOnWriteArrayList) this.subscriptionsByEventType.get(eventType);        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);        //如果之前没有该事件类型的订阅信息,则新加进去        if (subscriptions == null) {            subscriptions = new CopyOnWriteArrayList();            this.subscriptionsByEventType.put(eventType, subscriptions);        } else {            //判断之前是否注册过            for (Subscription subscription : subscriptions) {                if (subscription.equals(newSubscription)) {                    throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " +                            eventType);                }            }        }                subscriberMethod.method.setAccessible(true);        subscriptions.add(newSubscription);        //2、在typesBySubscriber中得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅;        List subscribedEvents = (List) this.typesBySubscriber.get(subscriber);        if (subscribedEvents == null) {            subscribedEvents = new ArrayList();            this.typesBySubscriber.put(subscriber, subscribedEvents);        }        subscribedEvents.add(eventType);        //3、检查这个事件是否是Sticky事件,如果是则从stickyEvents事件保存队列中取出该事件类型最后一个事件发送给当前订阅者。        if (sticky) {            Object stickyEvent;            synchronized (this.stickyEvents) {                stickyEvent = this.stickyEvents.get(eventType);            }            Object stickyEvent;            if (stickyEvent != null)                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());        }    }

现在我们应该明白了,其实EventBus的注册,核心是两点:

1、找到该事件类型(Event)的所有注册者(Subcriber),把当前注册者添加进去
2、找到该注册者注册的所有事件类型(EventType),把当前的新事件类型添加进去

至此,注册过程才算真正完成,接下来我们看看post过程。


发布事件

这里写图片描述

相比register过程,post就比较简单了:

public void post(Object event) {        //得到当前线程的事件队列        List eventQueue = (List) this.currentThreadEventQueue.get();        eventQueue.add(event);        BooleanWrapper isPosting = (BooleanWrapper) this.currentThreadIsPosting.get();        //如果当前事件队列正在分发,不作处理        if (isPosting.value) {            return;        }        boolean isMainThread = Looper.getMainLooper() == Looper.myLooper();        isPosting.value = true;        try {            //否则开始循环事件队列            while (!eventQueue.isEmpty())                //post核心                postSingleEvent(eventQueue.remove(0), isMainThread);        } finally {            isPosting.value = false;        }    }

我们主要看看postSingleEvent方法:

private void postSingleEvent(Object event, boolean isMainThread) throws Error {        Class eventClass = event.getClass();        //找到该事件的所有父类和接口        List eventTypes = findEventTypes(eventClass);        boolean subscriptionFound = false;        int countTypes = eventTypes.size();        for (int h = 0; h < countTypes; h++) {            Class clazz = (Class) eventTypes.get(h);            CopyOnWriteArrayList subscriptions;            synchronized (this) {                //获得每个事件的订阅者信息                subscriptions = (CopyOnWriteArrayList) this.subscriptionsByEventType.get(clazz);            }            CopyOnWriteArrayList subscriptions;            if (subscriptions != null) {                for (Subscription subscription : subscriptions) {                    //发布给每个订阅者                    postToSubscription(subscription, event, isMainThread);                }                subscriptionFound = true;            }        }        if (!subscriptionFound) {            Log.d(TAG, "No subscripers registered for event " + eventClass);            if ((eventClass != NoSubscriberEvent.class) && (eventClass != SubscriberExceptionEvent.class))                post(new NoSubscriberEvent(this, event));        }    }

我们继续看核心代码postToSubscription方法:

 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {        //根据订阅者信息的线程模式,用反射,执行响应函数方法        switch (subscription.subscriberMethod.threadMode.ordinal()) {            case 1://发布者所在线程                                invokeSubscriber(subscription, event);                break;            case 2://主线程                if (isMainThread)                    invokeSubscriber(subscription, event);                else {                    this.mainThreadPoster.enqueue(subscription, event);                }                break;            case 3:  //后台线程                if (isMainThread)                    this.backgroundPoster.enqueue(subscription, event);                else {                    invokeSubscriber(subscription, event);                }                break;            case 4:  //异步                this.asyncPoster.enqueue(subscription, event);                break;            default:                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);        }    }

至此我们看到了发布事件的真面目,说白了就是,EventBus根据发布事件的事件类型,找到我们注册的响应函数;我们的响应函数会根据响应函数名,自动判断以何种线程模式执行自己。这就给我们使用者带来很大方便,使得我们可以只用关注响应函数的实现。

最后我们再看看这些概念:

1、HandlerPoster:事件主线程处理,对应ThreadMode.MainThread。继承自 Handler,enqueue 函数将事件放到队列中,并利用 handler 发送 message,handleMessage 函数从队列中取事件,invoke 事件响应函数处理。

2、AsyncPoster:事件异步线程处理,对应ThreadMode.Async,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。

3、BackgroundPoster:事件 Background 处理,对应ThreadMode.BackgroundThread,继承自 Runnable。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。与 AsyncPoster.java 不同的是,BackgroundPoster 中的任务只在同一个线程中依次执行,而不是并发执行。

4、PendingPost:订阅者和事件信息实体类,并含有同一队列中指向下一个对象的指针。通过缓存存储不用的对象,减少下次创建的性能消耗。

5、PendingPostQueue:通过 head 和 tail 指针维护一个PendingPost队列。HandlerPoster、AsyncPoster、BackgroundPoster 都包含一个此队列实例,表示各自的订阅者及事件信息队列,在事件到来时进入队列,处理时从队列中取出一个元素进行处理。


后记

到此为止,EventBus源码就分析完了,这篇源码分析是博主参照codeKK的分析,读完EventBus源码,得出的记录贴,一方面加深下自己的印象,一方面以希望能给别人带来一些不一样的感悟吧。

3 4
原创粉丝点击