EventBus源码分析

简介

前面我学习了如何使用EventBus,还有了解了EventBus的特性,那么接下来我们一起来学习EventBus的源码,查看EventBus的源码,看看EventBus给我们带来什么惊喜以及编程思想。

EventBus-Android的发布 - 订阅

这个图我们从一开始就一直放置在上面了。我们在来回顾一下,EventBus的官网是怎么定义它的呢?
EventBus是Android和Java的发布/订阅(观察者模式)事件总线。
我们大概了解了EventBus的构建思想。接下来我们进入源码学习吧。

进入源码分析

我们从EventBus的注册开始入手。

EventBus.getDefault().register(this);
public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {//创建了EventBus实例,进入下面的方法defaultInstance = new EventBus(); }}}return defaultInstance;}

上面的方法是一个双重校验的单例。

public EventBus() {this(DEFAULT_BUILDER); //private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();}

DEFAULT_BUILDEREventBusBuilder.class实例来创建的,这个类非常重要:使用自定义参数创建EventBus实例,还允许自定义默认EventBus实例,如前面的例子使用索引、配置EventBus的配置&事件的优先级&使用索引(四)等就是通过这个类来实现的,大家可以回顾一下。进入初始化一下必要的参数的构造方法EventBus(EventBusBuilder builder),如下

EventBus(EventBusBuilder builder) {logger = builder.getLogger(); /初始化LoggersubscriptionsByEventType = new HashMap<>(); //存储订阅事件的typesBySubscriber = new HashMap<>(); //存储相关订阅者class列表,key是注册者的对象,value是订阅者的class列表stickyEvents = new ConcurrentHashMap<>();  //粘性事件//下面这4个实例就是我们设置方法``threadMode = ThreadMode.xxx``//*1mainThreadSupport = builder.getMainThreadSupport();mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;backgroundPoster = new BackgroundPoster(this);asyncPoster = new AsyncPoster(this);//获取注册者信息索引(添加由EventBus的注释预处理器生成的索引)indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;//初始订阅方法查找器。这个类主要具有查找订阅的方法,继续往下看subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);logSubscriberExceptions = builder.logSubscriberExceptions; //是否有日记订阅,默认truelogNoSubscriberMessages = builder.logNoSubscriberMessages;//是否有日记信息,默认 truesendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;//是否发送订阅异常事件,默认truesendNoSubscriberEvent = builder.sendNoSubscriberEvent;  //是否发送没有订阅事件,默认truethrowSubscriberException = builder.throwSubscriberException; //是否抛出异常处理//默认情况下,EventBus会考虑事件类层次结构(将通知超类的注册者)。 关闭此功能将改善事件的发布。对于直接扩展Object的简单事件类,我们测量事件发布的速度提高了20%。eventInheritance = builder.eventInheritance; executorService = builder.executorService;    //创建线程池}    

我们将此段代码逐步分析.
这步主要是进行初始化话一下必要的参数,如代码注解所示。
下面这段代码就是我们常用的@Subscribe(threadMode = ThreadMode.xxx);初始化。一般常用的就是以下4种。

mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this); 
asyncPoster = new AsyncPoster(this);

我们来看看builder.getMainThreadSupport()方法返回的是MainThreadSupport接口,表示为支持Android主线程。
上面的4个线程中都持有 PendingPostQueue 等待发送的队列实例。
mainThreadSupport.createPoster(this)创建一个HandlerPoster而该类继承了Handle,并且初始化了一个等待发布队列。代码如下。

public interface MainThreadSupport {boolean isMainThread();Poster createPoster(EventBus eventBus);class AndroidHandlerMainThreadSupport implements MainThreadSupport {private final Looper looper;public AndroidHandlerMainThreadSupport(Looper looper) {this.looper = looper;}@Overridepublic boolean isMainThread() {return looper == Looper.myLooper();}@Overridepublic Poster createPoster(EventBus eventBus) {return new HandlerPoster(eventBus, looper, 10);}}
}
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);

上面代码在这里主要初始化订阅的方法查找器。下面会讲解到它是如何进行订阅方法的。
我们这这里知道了EventBus初始化,然后相关的实例的创建,接下来我我们进入到register(this)方法的调用如下方法。

public void register(Object subscriber) {//获取当前注册者对象的Class。Class<?> subscriberClass = subscriber.getClass(); //通过该注册者的`Class`来获取当前注册者里面由`@Subscriber`注解绑定的方法。List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//必须在线程同步中进行。synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}

上面的代码表示的是给注册者接收事件。 传递当前所注册的对象,如Activity、Fragment

  • 注意: 注册者如果不再接收信息,必须调用unregister(Object)方法,表示解除注册则该订阅者将不再接收到数据了,如果不进行解除将可能出现内存泄漏。一般在onDestroy方法解除注册后面会讲解到。 在注册者中拥有必须由@Subscribe注解的方法。@Subscribe还允许配置ThreadMode和优先级、是否是粘性行为。

接着,我们进入List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);方法。

//private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); //线程安全List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {//从Map集合中获取订阅方法列表List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);//判断当前获取方法是否为空if (subscriberMethods != null) {return subscriberMethods;}//通过EventBusBuilder.ignoreGeneratedIndex//是否忽略配置索引,默认是忽略索引if (ignoreGeneratedIndex) {//1.通过反射获取方法列表subscriberMethods = findUsingReflection(subscriberClass);} else {//2.通过索引方式获取订阅方法列表subscriberMethods = findUsingInfo(subscriberClass);}//是否订阅方法为空,如果为空则表示该订阅者里面的方法都不符合EventBus订阅方法的规则if (subscriberMethods.isEmpty()) {throw new EventBusException("Subscriber " + subscriberClass+ " and its super classes have no public methods with the @Subscribe annotation");} else {//存储到集合中METHOD_CACHE.put(subscriberClass, subscriberMethods);return subscriberMethods;}
}

我们逐步分析该段代码。
该段代码通过注册者的类来获取当前dingy的方法列表,如果不为空则直接返回订阅方法。
通过反射来查找订阅方法,如果该方法为空则抛出异常:该订阅者的方法和其超类没有@Subscriber注解的共有方法(即表示不符合EventBus订阅方法的规则)。如果不为空则存储到ConcurrentHashMap集合中。
这里的是由ConcurrentHashMap集合存储是以当前订阅者的classkey,而将其由@Subscriber绑定的方法添加到List中,,就是集合的value。必须保持线程安全的,所以这里使用了ConcurrentHashMap

  • 1.是否忽略索引设置,该方法表示忽略设置索引,进入该方法findUsingReflection(Class<?> subscriberClass),通过反射进行获取List<SubscriberMethod>
  • 注:设置索引在EventBus的配置&事件的优先级&使用索引(四)这里说的,大家可以去看看。
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {FindState findState = prepareFindState();//准备查找状态findState.initForSubscriber(subscriberClass);// 初始化订阅者信息while (findState.clazz != null) {//循环获取订阅者的class是否为空//通过反射获取订阅方法,// 3.该方法下面分析findUsingReflectionInSingleClass(findState); findState.moveToSuperclass(); //删除超类class}获取订阅方法listreturn getMethodsAndRelease(findState);}

上面的代码主要是通过反射来获取相关的订阅方法,里面由静态内部类FindState进行管理相关信息,由于上面的方法和下面索引的方法都将调用同一个方法,所以放在下面来将讲解,请看下面信息。

  • 2.是否忽略索引设置,该方法表示的设置了索引,进入findUsingInfo(Class<?> subscriberClass)方法,如下。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {FindState findState = prepareFindState(); //准备查找状态findState.initForSubscriber(subscriberClass);// 初始化订阅者信息while (findState.clazz != null) { //循环获取订阅者的class是否为空findState.subscriberInfo = getSubscriberInfo(findState);//获取获取订阅者信息,这里如果使用添加索引的话,这里将能获取到索引的相关信息。if (findState.subscriberInfo != null) { //获取订阅方法SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();for (SubscriberMethod subscriberMethod : array) {//进行等级检查,并存储到Map集合中,该方法checkAdd是FindState.class中的方法 ,key是@Subscriber中参数的class,value是该subscriberMethod.method,也就是@Subscribe中的方法如:public void onMessageEvent(MessageEvent event)if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {//将该订阅方法添加到列表中findState.subscriberMethods.add(subscriberMethod);}}} else {//3.通过使用反射的方式来获取,和上面忽略索引调用到同一个方法,这里将解析findUsingReflectionInSingleClass(findState);}//清除父类的方法findState.moveToSuperclass();}//获取订阅方法listreturn getMethodsAndRelease(findState);}

分析本段代码。
首先进入查找的准备状态,通过默认状态池返回状态信息,主要存储的是FindState实例。主要存储的有订阅者的class、订阅者信息SubscriberInfo。订阅者@Subscribe方法列表List<SubscriberMethod>等.
如果当前订阅者class不为空,则将获取到订阅者信息。这里分两种情况。
如果使用添加索引的话,getSubscriberInfo(findState)该方法将获取到订阅信息,如果没有使用索引的话则将调用findUsingReflectionInSingleClass(findState);该方法来获取信息

  • 使用添加索引,并返回订阅方法,该方法如下
 //获取订阅方法SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();

由于AbstractSubscriberInfo类实现了SubscriberInfo接口,而SimpleSubscriberInfo继承了AbstractSubscriberInfo并实现了getSubscriberMethods方法,代码如下:
本段代码在SimpleSubscriberInfo.class中。返回订阅方法数组SubscriberMethod[]

@Overridepublic synchronized SubscriberMethod[] getSubscriberMethods() {int length = methodInfos.length;SubscriberMethod[] methods = new SubscriberMethod[length];for (int i = 0; i < length; i++) {SubscriberMethodInfo info = methodInfos[i];methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,info.priority, info.sticky);}return methods;}
  • 3.如果没有添加索引则进入该方法findUsingReflectionInSingleClass(findState);

通过反射来获取订阅者方法。这个方法有点长,慢慢看。

private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// This is faster than getMethods, especially when subscribers are fat classes like Activities//该方法代替getMethods()方法,获取该订阅者class全部方法。methods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149methods = findState.clazz.getMethods(); //获取该订阅者class全部方法findState.skipSuperClasses = true;  //设置跳过超类}for (Method method : methods) {int modifiers = method.getModifiers();//获取修饰符if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //通过与运算判断当前的修饰符,是否符合EventBus方法定义Class<?>[] parameterTypes = method.getParameterTypes(); //获取参数类型if (parameterTypes.length == 1) { //因为EventBus方法中必须有参数,所以当参数为1时,符合要求//通过注解的形式@Subscribe获取Subscribe,默认的Subscribe为(priority==0,sticky=false,threadMode=POSTING),就是说ThreadMode为POSTING,粘性为false,优先级为0,如果方法中设置了相应的值,则将是你设置的值。如threadMode=MAIN。Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);if (subscribeAnnotation != null) {Class<?> eventType = parameterTypes[0]; //获取当前参数的class//进行等级检查,并存储到Map集合中,该方法checkAdd是FindState.class中的方法 ,key是@Subscriber中参数的class,value是该subscriberMethod.method,也就是@Subscribe中的方法如:public void onMessageEvent(MessageEvent event)if (findState.checkAdd(method, eventType)) { // 获取ThreadMode ,如MAINThreadMode threadMode = subscribeAnnotation.threadMode();//将订阅方法添加到列表中findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));}}} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { //不符合EventBus订阅方法的规则要求。抛出异常,提示该方法必须是@Subscribe注解,并且需要一个参数String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException("@Subscribe method " + methodName +"must have exactly 1 parameter but has " + parameterTypes.length);}} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {//是一种非法的@Subscribe方法:必须是公共的,非静态的,非抽象的String methodName = method.getDeclaringClass().getName() + "." + method.getName();throw new EventBusException(methodName +" is a illegal @Subscribe method: must be public, non-static, and non-abstract");}}}

该段代码主要是获取该注册者的全部方法,并进行筛选出来符合EventBus的订阅方法的规则,通过注解的形式来获取订阅方法,最后添加到查找状态的列表中。
首先获取的是修饰符,参数类型,再通过注解的形式来获取订阅方法。如下注解Subscribe ,如果不符合EventBus相关订阅方法的规则将抛出异常提示,是否在方法上书写了错误的方法。
接下来我们进入看看@Subscribe 订阅方法是通过注解的形式来设置的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {//ThreadMode ,默认POSTINGThreadMode threadMode() default ThreadMode.POSTING;//粘性事件,默认falseboolean sticky() default false;//优先级,默认0int priority() default 0;
}

接下来进入getMethodsAndRelease(FindState findState)

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {//获取到订阅方法列表List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);   findState.recycle(); //清空回收相关信息synchronized (FIND_STATE_POOL) {for (int i = 0; i < POOL_SIZE; i++) {if (FIND_STATE_POOL[i] == null) {FIND_STATE_POOL[i] = findState; //将查找状态器findState,添加到状态池FIND_STATE_POOL中,为下次直接从查找状态池中获取break;}}}return subscriberMethods; // 返回订阅方法列表}

上面该方法通过FindState静态内部类获取了订阅的列表,然后被存储到了ConcurrentHashMap集合中。并且存储到状态池中,为方便下次读取。并且回收资源。
获取到List<SubscriberMethod>订阅列表后,再次回到了注册方法中register(Object subscriber)进入到方法subscribe(subscriber, subscriberMethod);该方法通过循环进行遍历

  • 注 此处必须是在线程同步中进行,所以添加synchronized。
//这里的参数subscriber表示的是订阅者,如Activity等,subscriberMethod表示订阅方法,该方法就是在subscriber里面
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType; //获取事件类型,也就是参数的classSubscription newSubscription = new Subscription(subscriber, subscriberMethod);//通过事件类型参数class获取CopyOnWriteArrayList<Subscription>列表,该类大家可以研究一下,//是写时复制容器,即当我们往`CopyOnWriteArrayList`容器添加元素时,先从原有的数组中拷贝一份出来,然后在新的容器做写操作(添加元素),写完之后,再将原来的数组引用指向到新数组的形式存储数据的,它是线程安全的。CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions); //添加到Map集合中} else {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);//优先级高的添加添加到CopyOnWriteArrayList容器最前面,只有一个的时候就是添加在第一位了。break;}}//获取订阅事件的class列表,即订阅方法的参数class列表List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();//存储到Map集合,key是订阅者(即上面传递的Activity对象),value是订阅事件列表typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType); //将该事件类型class添加到订阅事件列表中判断当前方法是否是粘性的if (subscriberMethod.sticky) {if (eventInheritance) {//必须考虑eventType的所有子类的现有粘性事件// 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();isAssignableFrom(Class<?> cls)方法表示的类型是否可以通过标识转换或通过扩展引用转换为此类对象表示的类型,则返回trueif (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();//将粘性事件添加到订阅者列表中checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {//将粘性事件添加到订阅者列表中Object stickyEvent = stickyEvents.get(eventType);checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}

上面的方法中主要是通过获取"事件类型"即订阅方法中的参数class。然后将其添加到Map集合中,这里用到了CopyOnWriteArrayList(原理:是写时复制容器,即当我们往CopyOnWriteArrayList容器添加元素时,先从原有的数组中拷贝一份出来,然后在新的容器做写操作(添加元素),写完之后,再将原来的数组引用指向到新数组的形式存储数据的,它是线程安全的。)
然后进行判断是否设置优先级,遍历并添加到subscriptions列表中。
通过当前的注册者,获取注册者class列表,并添加到typesBySubscriber集合中,key是注册者的class,如:Activity,而value就是订阅方法中的参数class的列表。这个地方说明了,一个注册者,可以拥有多个订阅事件(方法),将其绑定起来,存储到Map集合中。最后将该参数class添加到subscribedEvents.add(eventType);列表中。
下面方法是检查粘性事件订阅发送事件方法,如下

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {if (stickyEvent != null) {// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)// --> Strange corner case, which we don't take care of here.//这段代码我们到post发送事件的调用的时候讲解。postToSubscription(newSubscription, stickyEvent, isMainThread());}}
  • 注:这里主要是检查是否发送粘性发送事件,方法 postToSubscription我们到发送事件POST方法调用时讲解。
  • 事件发送

接下来我进入post方法,事件发送。此时,我们回忆一下简介中的图:
post()==>EventBus==>将分发到各个订阅事件中。也就是订阅方法。我们来看看是如何进行操作的。代码如下

EventBus.getDefault().post(new MessageEvent(message));

通过以上代码我大概的知道,post()方法里面的参数是一个实体的对象,而该实体就是我们在订阅方法中的参数实体。你发现了什么了吗?
前边我们已经分析了,该注册者的订阅方法主要的存储过程,接下来我们一起进入探究吧。方法跟踪进入post()方法。

/** Posts the given event to the event bus. *///给订阅事件,发送事件总线public void post(Object event) {//获取当前线程,即通过ThreadLocal本地线程来管理对应的事件线程,并且初始化发送线程状态PostingThreadStatePostingThreadState 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 {while (!eventQueue.isEmpty()) {//发送事件,发送完一个删除一个,直到发送全部停止循环postSingleEvent(eventQueue.remove(0), postingState);}} finally {//设置标志改变postingState.isPosting = false;postingState.isMainThread = false;}}}

currentPostingThreadState.get()方法中我们知道

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};

通过ThreadLocal这里就叫"本地线程"吧,来管理当前的发送线程状态,每个发送线程将得到对应一个PostingThreadState,而该PostingThreadState管理eventQueue信息,该信息主要存储的是发送事件。
通过eventQueue.add(event)存储该发送事件以后完成以后,就判断当前的发送事件状态是否是正在发送中,如果还没发送则当该eventQueue不为为空的时候,进入循环发送该事件,发送完一个就删除一个,直到发送完成为止。
进入方法postSingleEvent(Object event, PostingThreadState 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) {logger.log(Level.FINE, "No subscribers registered for event " + eventClass);}if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));}}}

上面的方法首先获取该事件类型的class然后传递到lookupAllEventTypes(eventClass)方法中,通过该方法获取当前发送事件的class,包括父类、实现的接口等等列表。所以一般情况下会返回当前发送的事件,和Object.class(注:当然了,如果该事件实现接口的话,也会包括实现的接口的。)列表即List<Class<?>>,然后进行遍历进行或运算。
我们先进入lookupAllEventTypes(eventClass)方法,看看返回的事件类型,并且知道他存储到Map集合中,即key是当前事件class,value是该事件的所有类型,包括父类,接口等。

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {//private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>(); key是当前事件类型的class,valu则是事件的所有类型,包括父类,接口等。本事例会得到的是MessageEvent.class、和Object.classsynchronized (eventTypesCache) {List<Class<?>> eventTypes = eventTypesCache.get(eventClass);if (eventTypes == null) {eventTypes = new ArrayList<>();Class<?> clazz = eventClass;while (clazz != null) {eventTypes.add(clazz); //添加到列表中//添加接口addInterfaces(eventTypes, clazz.getInterfaces());clazz = clazz.getSuperclass(); //获取父类class}eventTypesCache.put(eventClass, eventTypes); //存储d}return eventTypes;}}

看看方法postSingleEventForEventType(event, postingState, clazz),发送事件

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {//通过当前的事件class,获取订阅事件列表subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {//对PostingThreadState进行赋值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;}}return true;}return false;}

大家是否还记得在注册的时候出现的CopyOnWriteArrayList<Subscription>在这里就用到了,通过发送事件的class获取CopyOnWriteArrayList容器里面的订阅事件信息,包括注册者对象,订阅方法等。然后遍历并进行发送事件。在此方法发布postToSubscription()事件

 /*** subscription:发布订阅的事件处理器* event:当前发布的事件* isMainThread:是否是在主线程中*/
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING:invokeSubscriber(subscription, event);break;case MAIN:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED:if (mainThreadPoster != null) {mainThreadPoster.enqueue(subscription, event);} else {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(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);}}

好了,这就是之前我们在注册中有一个地方遗留下的,就是判断是否是粘性状态是调用的方法,大家往回看,就看到了在方法checkPostStickyEventToSubscription中有调用到该方法。
这里会根据,在方法中所选的threadMode进行调用,

  • 1.MAIN,如果当前是否是UI线程,则会进入到invokeSubscriber(subscription, event);方法中。否则将调用mainThreadPoster.enqueue(subscription, event);这个方法将交给HandlerPoster.class里面的enqueue(Subscription subscription, Object event)方法,HandlerPoster.class现实了该接口,移交给handle完成,大家可以进入HandlerPoster.class看看,这里就不说了。
  • 2.POSTING直接调用 invokeSubscriber(subscription, event);
  • 3.MAIN_ORDERED如果当前mainThreadPoster不为空则调用 mainThreadPoster.enqueue(subscription, event);MAIN进入HandlerPoster.class,反之调用invokeSubscriber(subscription, event);
  • 4.BACKGROUND如果当前是主线程,则调用backgroundPoster.enqueue(subscription, event);,反之调用invokeSubscriber(subscription, event);
  • 5.ASYNC,异步方法调用,执行 asyncPoster.enqueue(subscription, event);,方法进行跟踪,该类有实现了Runnable接口,然后调用eventBus.invokeSubscriber(pendingPost);,最终将调用了invoke方法。
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);}}

invoke()这是一个本地的方法,将调用C/C++的到方法进行发布。最后将进入到订阅方法中。并传递该事件到到订阅的方法中。
好了,这里我们将post发布事件的方法简单的分析了一下。整个EventBus的分析差不多完成了,但是,我们还有一点不能忘记就是解除绑定。接下来我们来看看解除绑定的方法。

  • 注:该方法一般生命周期的onDestroy()方法中进行解除绑定。

代码如下:

EventBus.getDefault().unregister(this);

直接进入unregister(this)

/** Unregisters the given subscriber from all event classes. */public synchronized void unregister(Object subscriber) {List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);if (subscribedTypes != null) {for (Class<?> eventType : subscribedTypes) {unsubscribeByEventType(subscriber, eventType);}typesBySubscriber.remove(subscriber);} else {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}}
    /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. *//*** subscriber:指代当前解除注册的对象如Activity,* eventType:发送的事件类型,post函数的实体对象class*/
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions != null) {int size = subscriptions.size();for (int i = 0; i < size; i++) {Subscription subscription = subscriptions.get(i);if (subscription.subscriber == subscriber) {subscription.active = false;subscriptions.remove(i); //删除i--;  //目的是减少遍历次数size--;}}}
}

解除绑定,这里很简单。这里就不一一进行解释了,^_^。

总结

1.本篇文章主要对EventBus源码进行简单的分析。主要是进行了观察者模式的一些高级运用。如果大家对观察者模式理解不怎么清楚可以进入这里看看简单的案例观察者模式,内容非常简单。
2.相关的EvenBut的使用,请看之前的内容。如EventBus认识(一)EventBus的ThreadMode使用以及分析(二)等等。
3.学习本篇文章中可以认识到一些常用的类如CopyOnWriteArrayListThreadLocal等,到时可以深入研究一下,有助我们提高。
4.一些编程思想,设计模式值得我们去学习的,如单例模式EventBus双重校验、建造者模式,EventBus构造器的时候用到,用了初始化各个参数等等。面向接口编程而不针对实现编程。
5.如果有什么问题希望大家进行指正,好好学习,一起进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537623.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue.js 概述与 MVVM 模式

一、Vue.js 1. Vue.js 是什么 Vue.js 是一个轻巧、高性能、可组件化的 MVVM 库&#xff0c;拥有非常容易上手的 API&#xff1b; Vue.js是一个构建数据驱动的 Web 界面的库。 2. Vue.js 的特性 1. 轻量级的框架 2. 双向数据绑定 3. 指令 4. 插件化&#xff08;组件化&#…

ipad录屏软件_如何在 Mac 上优雅的截图和录屏

在使用电脑的时候&#xff0c;很多时候我们都需要截屏或者录屏分享。所以你可能已经听过 snip&#xff0c;或者经常使用 QQ、微信 等软件来实现截屏。但是今天我们一起来看一下&#xff0c;怎么使用系统提供的功能&#xff0c;来优雅的实现截图和录屏。最开始的这几篇都比较基础…

安卓平板微信和手机微信同时登陆_Mac电脑如何同时登陆多个微信或QQ

现如今&#xff0c;微信和QQ已经不单单是简单的社交软件了&#xff0c;也成了工作必备软件&#xff0c;工作中用它传个文件&#xff0c;发个通知&#xff0c;不知道有多方便。但是很多人希望将工作圈和生活圈分开&#xff0c;有人想出拿两部手机的方法&#xff0c;倒是可以&…

苹果x人脸识别突然失灵_教你解决mac电脑键盘失灵的问题

无论是使用WIN系统还是苹果电脑都有可能遇到键盘失灵&#xff0c;无法使用的问题&#xff0c;那么苹果电脑键盘失灵该怎么解决呢&#xff1f;这里macw小编为大家带来了苹果电脑键盘失灵解决教程&#xff0c;赶紧收藏起来吧&#xff01;无线键盘失灵解决教程1、确保蓝牙已启用并…

产品文档如何说清楚产品业务?关注这几点就够了

如果产品文档没把产品业务说清楚会有什么影响&#xff1f; 常见的&#xff1a;产品不符合业务&#xff08;实际使用场景&#xff09;&#xff0c;验收不通过&#xff0c;需要加班修改&#xff0c;调整。产品经理被骂。 严重的&#xff1a;甲方爸爸受不了了&#xff0c;换供应…

高德百度地图如何获取附近小区酒店银行等?

概述 实现以下功能&#xff1a;根据当前定位&#xff0c;查找附近小区 实现步骤 高德&#xff0c;百度地图实现方式相同&#xff0c;步骤都是如下&#xff1a; 1.获取当前位置的经纬度 2.查询poitype编码。 &#xff08;小区&#xff0c;写字楼&#xff0c;银行&#xff0…

angular 字符串转换成数字_Python基础语法大全:字符串的处理与使用

本文来自牛鹭学院学员&#xff1a;邓瑞杰字母处理string.capitalize()返回元字符串&#xff0c;且将字符串第一个字母转为大写&#xff0c;其它字母小写string.title()返回元字符串&#xff0c;且将字符串第一个字母转为大写&#xff0c;其它字母小写string.swapcase()用于对字…

1亿像素的真相

世界那么大&#xff0c;谢谢你来看我&#xff01;&#xff01;关注我你就是个网络、电脑、手机小达人 乔布斯曾说&#xff0c;“手机拍照上主打高像素&#xff0c;是手机厂商利用广大消费者对照片成像原理知识漏洞的表现。” 小米发布了一款有1亿像素的概念型手机&#xff0c…

python实现链表的删除_Python中 为我们提供了一些独特的解决方案的方法特性

实际上&#xff0c;在日常的工作中&#xff0c;我们很多需求&#xff0c;无论是常见的、还是不常见的&#xff0c;Python 都为我们提供了一些独特的解决方案&#xff0c;既不需要自己造轮子&#xff0c;也不需要引入新的依赖(引入新的依赖势必会增加项目的复杂度)。但是 Python…

打通C/4HANA和S/4HANA的一个原型开发:智能服务创新案例

2019独角兽企业重金招聘Python工程师标准>>> 今年6月SAP发布C/4HANA之后&#xff0c;有顾问朋友们在公众号后台留言&#xff0c;询问C/4HANA如何同SAP的数字化核心S/4HANA系统结合起来&#xff0c;从而打通企业的前后端业务&#xff0c;帮助企业实现数字化转型。 有…

爬虫的重要思想

1.理论上讲只要网页上面能够看到的数据都是可以爬取的&#xff0c;因为所有看到的网页上的数据都是服务器发送到我们电脑上面的&#xff0c;只是有的数据加密过&#xff0c;很难解密。 2.在网页上无法看到或者无法获取的数据&#xff0c;爬虫同样不可能拿到&#xff0c;比如一…

python web环境傻瓜搭建_python——web 环境搭建

1.安装引用第三方库 selenium pip install selenium 2.下载浏览器驱动&#xff08;驱动版本和浏览器版本要对应参考链接 http://npm.taobao.org/mirrors/chromedriver/&#xff09; 查看浏览器驱动对应关系&#xff0c;本次已最新驱动版本作为教材讲解 版本2.42 发布时间2018.9…

项目管理十大知识领域,为何不含

问&#xff1a;项目管理十大知识领域&#xff0c;为何不含以下几项&#xff1a;立项管理&#xff0c;合同管理&#xff0c;文档管理&#xff0c;变更管理&#xff1f; 答&#xff1a; 立项完了才有项目&#xff1b; 合同管理包含在采购管理&#xff1b; 变更管理在整体管理&am…

在职场遇到一个总是说话带刺的同事怎么相处?

网友&#xff1a; 带刺的人如果总是扑空&#xff0c;或者总是戳进一团棉花&#xff0c;你说他&#xff0c;她还能坚持多久。职场重心是工作&#xff0c;是做事&#xff0c;不要为这些无聊的人事太费心&#xff0c;刺猬带着刺到处转&#xff0c;最后结果不会好的。 网友&#xf…

postgresql 怎么读_大数据采集和抽取怎么做?这篇文章终于说明白了!

本文来源于公众号【胖滚猪学编程】&#xff0c;转载请注明出处&#xff01; 关于数据中台的概念和架构&#xff0c;我们在大白话 六问数据中台和数据中台全景架构及模块解析&#xff01;一文入门中台架构师&#xff01;两篇文章中都说明白了。从这一篇文章开始分享中台落地实战…

ZooKeeper应用——解决分布式系统单点故障

1.单点故障问题什么是分布式系统中的单点故障&#xff1a;通常分布式系统采用主从模式&#xff0c;就是一个主控机连接多个处理节点。主节点负责分发任务&#xff0c;从节点负责处理任务&#xff0c;当我们的主节点发生故障时&#xff0c;那么整个系统就都瘫痪了&#xff0c;那…

生活之难:生活到底难在哪里

生活之难&#xff1a;生活到底难在哪里 一、总结 一句话总结&#xff1a;难在天性&#xff0c;难在竞争&#xff0c;难在积累&#xff0c;难在追求&#xff0c;难在自己 难在天性 人的天性就是好吃懒做好玩不动脑的&#xff0c;但是生存的压力&#xff08;食物&#xff0c;房子…

python中元祖 字典 列表的区别_Python中元祖,列表,字典的区别

原博文 2016-08-16 15:25 − Python中有3种內建的数据结构&#xff1a;列表、元祖和字典&#xff1a; 1.列表 list是处理一组有序项目的数据结构&#xff0c;即你可以在一个列表中存储一个序列的项目。 列表中的项目应该包括在方括号中&#xff0c;这样Python就知道你是指明一个…

Intellij IDEA展示类中的方法树形结构

在intellij Idea中叫Structure&#xff08;结构体&#xff09;&#xff0c;如下图&#xff1b; 也可以直接AltF7快捷键&#xff0c;这样默认会把Structure显示在屏幕下方&#xff0c;如下图操作就可以移动到右侧。 效果如下&#xff1a;

时间计算题100道_2019四校及分校自招开放日情况汇总(含时间安排、考试内容难度、到场人数等)...

点击上方“上海初升高”&#xff0c;选择“星标公众号”回复“加群”就能加入上万家长信赖的升学群受到疫情的影响&#xff0c;今年各市重点的自招开放日报名迟迟没有提上日程。但不管怎样&#xff0c;自招应该是不会取消的&#xff0c;以下是去年四校及分校自招开放日情况汇总…