当前位置: 代码迷 >> 综合 >> Android 源码解析:EventBus
  详细解决方案

Android 源码解析:EventBus

热度:62   发布时间:2023-12-16 04:02:30.0
EventBus源码解析

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

1 基本术语

事件(Event):又可称为消息,事件。事件类型(EventType)指事件所属的 Class。

订阅者(Subscriber):订阅某种事件类型的对象。

发布者(Publisher):发布某事件的对象,通过 post 接口发布事件。

2 工作流程



EventBus 负责存储订阅者、事件相关信息,订阅者和发布者都只和 EventBus 关联。订阅者首先调用 EventBus 的 register 接口订阅某种类型的事件,当发布者通过 post 接口发布该类型的事件时,EventBus 执行调用者的事件响应函数。


事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个 Sticky 事件。
当有发布者发布这类事件后,EventBus 会执行订阅者的 onEvent 函数,订阅者通过 register 接口订阅某个事件类型,unregister 接口退订。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。

主要方法:

EventBus.getDefault().register(this);//订阅事件
EventBus.getDefault().post(object);//发布事件
EventBus.getDefault().unregister(this);//取消订阅
onEventMainThread(){ //反射异步回调的方法
}

实现原理:

       首先我们在onCreate里面调用EventBus.getDefault().register(this); 接着在该类中调用回调方法onEventMainThread()等。目的是让EventBus扫描当前类,把所有onEvent开头的方法记录下来(使用Map,Key为方法的参数类型,Value中包含我们的方法)。

然后在onCreate执行完成以后,我们的onEventMainThread就已经以键值对的方式被存储到EventBus中了。

再然后,当一个其他任务在子线程执行完毕,调用EventBus.getDefault().post(new Item(xx)))时,EventBus会根据post中实参的类型,去Map中查找对于的方法,于是找到了我们的onEventMainThread,最终调用反射去执行我们的方法。

这样整个流程就完成了,实现了没有接口却能发生回调。

3.源码实现

类的关系图:




EventBus.java
EventBus 类负责所有对外暴露的 API,其中的 register()、post()、unregister() 函数配合上自定义的 EventType 及事件响应函数即可完成核心功能。


EventBus 默认可通过静态函数 getDefault 获取单例,当然有需要也可以通过 EventBusBuilder 或 构造函数新建一个 EventBus,每个新建的 EventBus 发布和订阅事件都是相互隔离的,即一个 EventBus 对象中的发布者发布事件,另一个 EventBus 对象中的订阅者不会收到该订阅。

源码如下:

/** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();
            }}}return defaultInstance;
}
使用了双重判断的方式,防止并发的问题,还能极大的提高效率。

/**  * Creates a new EventBus instance; each instance is a separate scope in which events are delivered.To * use a central bus, consider {
     @link #getDefault()}.  */ public EventBus() {this(DEFAULT_BUILDER);
}EventBus(EventBusBuilder builder) {subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
    typesBySubscriber = new HashMap<Object, List<Class<?>>>();
    stickyEvents = new ConcurrentHashMap<Class<?>, Object>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    subscriberMethodFinder = new SubscriberMethodFinder(builder.skipMethodVerificationForClasses);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}


EventBus 中对外 API,register()和post()是最为关键的方法。


(1) register 

分别表示订阅事件和取消订阅。register 最底层函数有三个参数,分别为订阅者对象、是否是 Sticky 事件、优先级。

源码如下:

public void register(Object subscriber) {register(subscriber, false, 0);
}
public void register(Object subscriber, int priority) {register(subscriber, false, priority);
}
private synchronized void register(Object subscriber, boolean sticky, int priority) {List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}
本质上都是调用了三个参数的方法。 PS:在此之前的版本 EventBus 还允许自定义事件响应函数名称,这版本中此功能已经被去除。
在注册方法中, 调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。 那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个List。
下面看看源码中 findSubscriberMethods()方法的实现。
/**  * 通过subscriberClass,去遍历subscriber类内部的所有方法,然后根据methodName去匹配.  * 匹配成功的封装成SubscriberMethod,最后返回一个List<SubscriberMethod>  *  * @param subscriberClass  * @return List<SubscriberMethod>*/ List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {String key = subscriberClass.getName();
  List<SubscriberMethod> subscriberMethods;
  synchronized (methodCache) {subscriberMethods = methodCache.get(key);
  }if (subscriberMethods != null) {return subscriberMethods;
  }subscriberMethods = new ArrayList<SubscriberMethod>();
  Class<?> clazz = subscriberClass;
  HashSet<String> eventTypesFound = new HashSet<String>();
  StringBuilder methodKeyBuilder = new StringBuilder();
  while (clazz != null) {String name = clazz.getName();
    if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {// Skip system classes, this just degrades performance
      break;
    }// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {String methodName = method.getName();
      if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {Class<?>[] parameterTypes = method.getParameterTypes();
          if (parameterTypes.length == 1) {String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
            ThreadMode threadMode;
            if (modifierString.length() == 0) {threadMode = ThreadMode.PostThread;
            } else if (modifierString.equals("MainThread")) {threadMode = ThreadMode.MainThread;
            } else if (modifierString.equals("BackgroundThread")) {threadMode = ThreadMode.BackgroundThread;
            } else if (modifierString.equals("Async")) {threadMode = ThreadMode.Async;
            } else {if (skipMethodVerificationForClasses.containsKey(clazz)) {continue;
              } else {throw new EventBusException("Illegal onEvent method, check for typos: " + method);
              }}Class<?> eventType = parameterTypes[0];
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(methodName);
            methodKeyBuilder.append('>').append(eventType.getName());
            String methodKey = methodKeyBuilder.toString();
            if (eventTypesFound.add(methodKey)) {// Only add if not already found in a sub class
              subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
            }}} else if (!skipMethodVerificationForClasses.containsKey(clazz)) {Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "." + methodName);
        }}}clazz = clazz.getSuperclass();
  }if (subscriberMethods.isEmpty()) {throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " + ON_EVENT_METHOD_NAME);
  } else {synchronized (methodCache) {methodCache.put(key, subscriberMethods);
    }return subscriberMethods;
  }
}
方法解析:
首先是要clazz.getMethods();去得到所有的方法,接着就开始遍历每一个方法了,去匹配封装了。分别判断了是否以onEvent开头,是否是public且非static和abstract方法,是否是一个参数。如果都符合要求,才进入封装匹配根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。最后,将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,最终放回。
注意下面的 clazz = clazz.getSuperclass();可以看到,会扫描所有的父类,不仅仅是当前类。

接着看看for循环中的subscribe(subscriber, subscriberMethod, sticky, priority)方法的实现:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {Class<?> eventType = subscriberMethod.eventType;
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
    if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<Subscription>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {if (subscriptions.contains(newSubscription)) {throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }}// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    // subscriberMethod.method.setAccessible(true);

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {if (i == size || newSubscription.priority > subscriptions.get(i).priority) {subscriptions.add(i, newSubscription);
            break;
        }}List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {subscribedEvents = new ArrayList<Class<?>>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }subscribedEvents.add(eventType);

    if (sticky) {if (eventInheritance) {// Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }}} else {Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }}
}
方法解析:

注意:subscriberMethod中保存了method, threadMode, eventType。

该方法首先根据subscriberMethod.eventType,去subscriptionsByEventType。去查找一个CopyOnWriteArrayList<Subscription> ,如果没有则创建。顺便把我们的传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority);

这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 这个Map其实就是EventBus存储方法的地方!

接下来,就是添加newSubscription;并且是按照优先级添加的。可以看到,优先级越高,会插到在当前List的前面。

然后,根据subscriber存储它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;这里主要用于isRegister的判断。

最后,就是判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。

register()总结:

EventBus扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;其中,包含了执行改方法所需的一切。



(2) post 方法


直接看EnvetBus中方法的实现源码:

/** Posts the given event to the event bus. */ public void post(Object event) {PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    if (!postingState.isPosting) {postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");
        }try {while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0), postingState);
            }} finally {postingState.isPosting = false;
            postingState.isMainThread = false;
        }}
}

方法解析:

在register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。

其中,currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState;PostingThreadState包含了一个eventQueue和一些标志位。

首先,把传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。然后判断当前是否是UI线程。

最后,遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。


接下来看看 postSingleEvent(eventQueue.remove(0), postingState)方法的实现:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }} else {subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }if (!subscriptionFound) {if (logNoSubscriberMessages) {Log.d(TAG, "No subscribers registered for event " + eventClass);
        }if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));
        }}
}

方法解析:

首先,根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class,以及父类和接口的Class类型;主要用于匹配包括父类。

接下来,遍历所有的Class,到subscriptionsByEventType去查找subscriptions;当我们register里面把方法保存到这个Map里了;

然后,遍历每个subscription,依次去调用postToSubscription(subscription, event, postingState.isMainThread);
这个方法就是去反射执行回调方法了,在register,if(sticky)时,也会去执行这个方法。

最后,看看postToSubscription(subscription, event, postingState.isMainThread)这个方法:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case PostThread:invokeSubscriber(subscription, event);
            break;
        case MainThread:if (isMainThread) {invokeSubscriber(subscription, event);
            } else {mainThreadPoster.enqueue(subscription, event);
            }break;
        case BackgroundThread: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去判断应该在哪个线程去执行该方法。

case PostThread直接反射调用;也就是说在当前的线程直接调用该方法;

case MainThread:首先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方法。说白了就是通过Handler去发送消息,然后执行的。

 case BackgroundThread如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用executorService = Executors.newCachedThreadPool();

 case Async:将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。

 BackgroundThread和Async有什么区别呢?

BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。

Async则会动态控制并发。

总结:到此,Eventbus类源码分析的就差不多了,Eventbus类是这个事件发布库的门面类,包含了所有重要的与外界打交道的api.

register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。也可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。


EventBus中其他的类的介绍:

SubscriberMethod.java

订阅者事件响应函数信息,包括响应方法、线程 Mode、事件类型以及一个用来比较 SubscriberMethod 是否相等的特征值 methodString 共四个变量,其中 methodString 为 ${methodClassName}#${methodName}(${eventTypeClassName}。

Subscription.java

订阅者信息,包括 subscriber 对象、事件响应方法 SubscriberMethod、优先级 priority。

HandlerPoster.jva

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

 AsyncPoster.java

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

BackgroundPoster.java

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

 PendingPost.java

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

 PendingPostQueue.java

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

SubscriberExceptionEvent.java

当调用事件处理函数异常时发送的 EventBus 内部自定义事件,通过 post 发送,订阅者可自行订阅这类事件进行处理。

NoSubscriberEvent.java

当没有事件处理函数对事件处理时发送的 EventBus 内部自定义事件,通过 post 发送,订阅者可自行订阅这类事件进行处理。

 EventBusException.java

封装于 RuntimeException 之上的 Exception,只是覆盖构造函数,相当于一个标记,标记是属于 EventBus 的 Exception。

ThreadMode.java

线程 Mode 枚举类,表示事件响应函数执行线程信息,包括ThreadMode.PostThreadThreadMode.MainThreadThreadMode.BackgroundThreadThreadMode.Async四种。





  相关解决方案