EventBus源码解析

知识点汇总:

一:EventBus框架概述 

二:EventBus的注册实现原理 

三:EventBus的事件分发实现原理

四:项目扩展知识点 

五:扩展阅读


一:EventBus框架概述

描述:EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化Android事件传递,使项目接入和使用都十分方便,下面是官方的项目概述:

       从上面的官方解析图中,我们注意到项目的整体设计是基于订阅者模式进行设计的,在订阅者模式中,主要涉及到的核心模块主要有两部分:事件的订阅、解除订阅处理 和 事件的分发处理。

1、事件的订阅、解除订阅处理:该模块在订阅者模式中,主要是用来分类并保存项目中所有订阅者的信息,就好比有一千个读者订阅了报社的不同类型的报纸,报社需要对不同的订阅者进行分类并保存相关的订阅者信息,当读者解除了相关的订阅时,报社如何解除并删除相关分类的订阅信息,使下次发布报纸时不再发送给相关用户。 

2、事件的分发处理:该模块在订阅者模式中,主要是针对不同的订阅者,发送相关的订阅信息给到相应的订阅者中,就好比当前报社有一千个订阅者,报社需要根据不同的订阅者,快递不同的订阅报纸给到不同的订阅者手上。     

      上面通过现实生活中的例子,我们可以更加了解EventBus项目使用到的核心设计思想,从而让我们更好的了解具体的项目代码实现,下面我们就一起了解一下Eventbus是如何实现相关模块的。


二:EventBus的注册实现原理

下面我们先大体了解一下EventBus是如何实现订阅模块的设计的:

备注:在该项目中,我们可以看到不同的数据结构存放着不同的数据,其中比较重要的点在于我们需要知道最终发送订阅消息是根据订阅的函数参数作为分类发送订阅消息的,也就是说,不同的订阅者的分组是根据订阅的函数的参数类型进行分组的,而不是根据订阅函数所在的类进行分组的,然后在进行分组时,还需要根据不同订阅函数的ThreadMode、Sticky、Priority属性进行同一个分类的不同处理。

订阅相关函数代码:

解析:传入的参数是订阅函数所在的类对象,通过类对象,获取到相关的类的类型,查找到相关类类型所有的订阅函数数据,并通过对查找到的订阅函数,分别针对不同的订阅函数的参数和属性(ThreadMode、Sticky、Priority),实现不同的处理,并最终根据函数的参数实现对订阅者的分类并保存。

我们看一下如何通过订阅函数的类对象,查找到相关的订阅函数的,下面是实现代码:

解析:首先会去缓存查找是否有相关类的订阅函数数据,如果没有,下面提供的两种方式去获取订阅函数信息,分别是:

1、通过反射遍历相关对象中使用@subscribe注解的函数,从而获取订阅函数相关数据。

2、通过APT注解处理器动态生成的订阅函数的数据集合,从而获取订阅函数相关数据。

         这里为什么要设计两种实现方式,主要是考虑到反射的实现方式效率会比较低,所以才会出现第二种实现方式,有兴趣的朋友可以了解一下相关的具体代码实现。

下面就是订阅事件相关代码:

解析:通过遍历函数subscribe函数(参数为:订阅函数的类和订阅函数),把项目中所有的订阅函数封装在对象Subscription对象中,再根据订阅的事件类型对不同的订阅函数进行分组存放,保存在Map, CopyOnWriteArrayList> subscriptionsByEventType哈希表中,函数中还包含对订阅函数优先级、粘性等属性的处理,后续事件分发就通过subscriptionsByEventType表,根据事件类型找到所有订阅函数列表,并根据订阅函数的配置属性,依次分发事件。

三:EventBus的事件分发实现原理

备注:对于订阅事件的分发处理,由于在订阅模块已经根据事件类型把不同的订阅函数进行的分类存储了,在事件分发模块主要就是针对不同的订阅函数 和 相关订阅函数的不同属性的设置(ThreadMode、Priority),实现对不同订阅函数进行分发处理的过程,由于订阅函数的ThreadMode线程模式不同,不同的事件类型函数需要在不同的线程中的事件队列中排队(PendingPostQueue)处理。

下面我们就看看事件分发相关相关函数实现代码:

解析:上面代码通过获得发送分发事件的线程的分发事件队列,并将当前需要在不用线程分发的事件通过遍历按顺序依次执行,依次执行通过调用函数postSingleEvent实现,最后,不同的分发事件集合由于不同的订阅函数的属性配置不同,会在不同的线程队列中排队,并依次执行,核心函数实现在postToSubscription函数中。

postToSubscription函数的实现代码:

最终执行函数:

解析:从上面代码可以看出,由于不同的订阅函数的ThreadMode不同,代码中针对不同的属性分别做了不同的处理,处理的实现刚好对应五种不同的线程参数类型,并最终通过反射函数invoke实现对相关订阅函数的执行。


四:项目扩展知识点与答疑

4.1、项目中涉及到的数据结构有哪些,他们在什么场景使用比较合适?

1、CopyOnWriteArrayList

2、PendingPostQueue

1、CopyOnWriteArrayList数据结构:

      项目中的使用:Map, CopyOnWriteArrayList> subscriptionsByEventType,CopyOnWriteArrayList的关键点:

1、内部持有一个ReentrantLock lock = new ReentrantLock()

2、底层是用volatile transient声明的数组 array

3、读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

       在项目中通过事件分发对象类型,获取所有订阅的函数对象,通过读写分离,完成相关订阅对象在不同线程执行时数据的一致性,解决相关同步问题。

2、PendingPostQueue数据结构:

解析:该数据结构是个队列的数据结构,分别有参数队头和队尾参数,提供了入队和出队等函数的实现,在项目中通过该数据结构,可以同步的处理分发事件。


4.2、ThreadLocal的数据结构与使用场景?

项目的使用代码:

    private final ThreadLocal currentPostingThreadState = new ThreadLocal() {

        @Override

        protected PostingThreadState initialValue() {

            return new PostingThreadState();

        }

    };

ThreadLocal解析:多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

  ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。

原理概述:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们。除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。

   通过上面的对LocalThread的了解和项目中的使用,我们了解到,项目中需要通过LocalThread存储不同线程相关的分发事件队列,从而可以实现不同分发事件的线程可以同步的高效的执行相关的分发事件。


4.3、APT注解处理器的调试流程?

步骤一:在项目gradle.properties文件中,添加配置代码:

org.gradle.daemon=true

org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

-Dorg.gradle.debug=true


步骤二:在运行的主项目中,添加APT注解处理器的module模块,配置如下:(dependencies下)

annotationProcessor project(':eventbus-annotation-processor')


步骤三:点开顶部菜单的Run的子菜单Edit Configurations,配置远程调试项目:


步骤四:把自己配置的APT设置调试模式,并给相关Processor类添加断点

备注:有时候点击APT的调试模式时,会提示无法连接5005端口,导致无法进入调试模式,这是可以关闭暂用相关端口的应用或重新启动AS解决问题。

4.4、如何通过APT动态生成代码:MyEventBusIndex类?

解析:下面我们一起看看注解处理器的核心代码,下面截取出注解处理器回调函数process的相关核心代码。

解析:代码中主要有三个逻辑处理函数:

1、通过注解处理器相关参数:Set annotations, RoundEnvironment env,收集到项目中的所有订阅函数,并存放在数据结构methodsByClass中。

2、通过对订阅函数的修饰符、参数列表等过滤条件,汇总出不符合订阅条件的订阅函数。

3、通过processingEnv和BufferedWriter实现代码的动态生成。


4.5、AsyncPoster和BackgroundPoster的区别是什么?

解析:通过源码了解到,AsyncPoster是异步实现的事件分发,队列中的事件分发没有相关的同步处理限制,不需要等待前面的分发事件处理完成,就可以执行后面的分发事件,而BackgroundPoster则有相关的限制。


下面本人绘制出Eventbus项目的综合解析图:

五:扩展阅读

1、https://www.cnblogs.com/all88/p/5338412.html(EventBus3.0源码解析)

2、https://blog.csdn.net/tomatomas/article/details/53998585/(如何调试编译时注解处理器AnnotationProcessor)

3、https://www.cnblogs.com/fsmly/p/11020641.html#_label0(Java中的ThreadLocal详解)

4、https://juejin.cn/post/6844904201290514446(APT Android代码怎么调试)

你可能感兴趣的:(EventBus源码解析)