EventBus源码分析
简介
前面我学习了如何使用EventBus,还有了解了EventBus的特性,那么接下来我们一起来学习EventBus的源码,查看EventBus的源码,看看EventBus给我们带来什么惊喜以及编程思想。
这个图我们从一开始就一直放置在上面了。我们在来回顾一下,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_BUILDER是EventBusBuilder.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种。
我们来看看builder.getMainThreadSupport()方法返回的是MainThreadSupport接口,表示为支持Android主线程。
上面的4个线程中都持有 PendingPostQueue 等待发送的队列实例。
由mainThreadSupport.createPoster(this)创建一个HandlerPoster而该类继承了Handle,并且初始化了一个等待发布队列。代码如下。
上面代码在这里主要初始化订阅的方法查找器。下面会讲解到它是如何进行订阅方法的。
我们这这里知道了EventBus初始化,然后相关的实例的创建,接下来我我们进入到register(this)方法的调用如下方法。
上面的代码表示的是给注册者接收事件。 传递当前所注册的对象,如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集合存储是以当前订阅者的class为key,而将其由@Subscriber绑定的方法添加到List中,,就是集合的value。必须保持线程安全的,所以这里使用了ConcurrentHashMap。
- 1.是否忽略索引设置,该方法表示忽略设置索引,进入该方法findUsingReflection(Class<?> subscriberClass),通过反射进行获取List<SubscriberMethod>
- 注:设置索引在EventBus的配置&事件的优先级&使用索引(四)这里说的,大家可以去看看。
上面的代码主要是通过反射来获取相关的订阅方法,里面由静态内部类FindState进行管理相关信息,由于上面的方法和下面索引的方法都将调用同一个方法,所以放在下面来将讲解,请看下面信息。
- 2.是否忽略索引设置,该方法表示的设置了索引,进入findUsingInfo(Class<?> subscriberClass)方法,如下。
分析本段代码。
首先进入查找的准备状态,通过默认状态池返回状态信息,主要存储的是FindState实例。主要存储的有订阅者的class、订阅者信息SubscriberInfo。订阅者@Subscribe方法列表List<SubscriberMethod>等.
如果当前订阅者class不为空,则将获取到订阅者信息。这里分两种情况。
如果使用添加索引的话,getSubscriberInfo(findState)该方法将获取到订阅信息,如果没有使用索引的话则将调用findUsingReflectionInSingleClass(findState);该方法来获取信息
- 使用添加索引,并返回订阅方法,该方法如下
由于AbstractSubscriberInfo类实现了SubscriberInfo接口,而SimpleSubscriberInfo继承了AbstractSubscriberInfo并实现了getSubscriberMethods方法,代码如下:
本段代码在SimpleSubscriberInfo.class中。返回订阅方法数组SubscriberMethod[]。
- 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 订阅方法是通过注解的形式来设置的。
接下来进入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。
上面的方法中主要是通过获取"事件类型"即订阅方法中的参数class。然后将其添加到Map集合中,这里用到了CopyOnWriteArrayList(原理:是写时复制容器,即当我们往CopyOnWriteArrayList容器添加元素时,先从原有的数组中拷贝一份出来,然后在新的容器做写操作(添加元素),写完之后,再将原来的数组引用指向到新数组的形式存储数据的,它是线程安全的。)
然后进行判断是否设置优先级,遍历并添加到subscriptions列表中。
通过当前的注册者,获取注册者class列表,并添加到typesBySubscriber集合中,key是注册者的class,如:Activity,而value就是订阅方法中的参数class的列表。这个地方说明了,一个注册者,可以拥有多个订阅事件(方法),将其绑定起来,存储到Map集合中。最后将该参数class添加到subscribedEvents.add(eventType);列表中。
下面方法是检查粘性事件订阅发送事件方法,如下
- 注:这里主要是检查是否发送粘性发送事件,方法 postToSubscription我们到发送事件POST方法调用时讲解。
- 事件发送
接下来我进入post方法,事件发送。此时,我们回忆一下简介中的图:
post()==>EventBus==>将分发到各个订阅事件中。也就是订阅方法。我们来看看是如何进行操作的。代码如下
通过以上代码我大概的知道,post()方法里面的参数是一个实体的对象,而该实体就是我们在订阅方法中的参数实体。你发现了什么了吗?
前边我们已经分析了,该注册者的订阅方法主要的存储过程,接下来我们一起进入探究吧。方法跟踪进入post()方法。
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)中。
上面的方法首先获取该事件类型的class然后传递到lookupAllEventTypes(eventClass)方法中,通过该方法获取当前发送事件的class,包括父类、实现的接口等等列表。所以一般情况下会返回当前发送的事件,和Object.class(注:当然了,如果该事件实现接口的话,也会包括实现的接口的。)列表即List<Class<?>>,然后进行遍历进行或运算。
我们先进入lookupAllEventTypes(eventClass)方法,看看返回的事件类型,并且知道他存储到Map集合中,即key是当前事件class,value是该事件的所有类型,包括父类,接口等。
看看方法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方法。
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.学习本篇文章中可以认识到一些常用的类如CopyOnWriteArrayList、ThreadLocal等,到时可以深入研究一下,有助我们提高。
4.一些编程思想,设计模式值得我们去学习的,如单例模式EventBus双重校验、建造者模式,EventBus构造器的时候用到,用了初始化各个参数等等。面向接口编程而不针对实现编程。
5.如果有什么问题希望大家进行指正,好好学习,一起进步。
总结
以上是生活随笔为你收集整理的EventBus源码分析的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 10. Python面向对象
- 下一篇: 2019年1月3日