EventBus源码分析

本文详细介绍了EventBus的功能、概念和使用流程,包括事件(Event)、订阅者(Subscriber)和发布者(Publisher)的概念。重点分析了EventBus的注册和发布事件的过程,涉及到线程模式、事件类型以及处理机制。通过对源码的解读,揭示了EventBus如何解耦发布者和订阅者,简化事件传递。
摘要由CSDN通过智能技术生成

以下图表来自: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源码,得出的记录贴,一方面加深下自己的印象,一方面以希望能给别人带来一些不一样的感悟吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值