前面已经介绍了EventBus3.0开源库的详细使用,下面我们开始进入其源代码的学习,先看看EventBus3.0与2.x版本之间的区别。
项目地址https://github.com/greenrobot/EventBus
EventBus版本是3.0
EventBus 3.0与2.x的区别
注册订阅者
|
|
2.x版本中有四种注册方法,区分了普通注册和粘性事件注册,并且在注册时可以选择接受事件的优先级;
3.0版本中将粘性事件以及订阅事件的优先级用注解的方式实现,所以3.0版本中的注册就变得只有一个register()方法注册。
事件订阅方法
|
|
2.x版本中只有通过onEvent开头的方法会被注册,而且响应事件方法触发的线程通过onEventMainThread或onEventBackgroundThread这些方法名区分;
3.0版本中,通过@Subscribe注解来确定运行的线程threadMode,是否接收粘性事件sticky以及事件优先级priority,而且方法名不再需要使用onEvent开头,所以3.0提高了简单性和灵活性。
发送事件
发送事件和发送粘性事件在2.x和3.0版本中是相同的。
|
|
解除注册
2.x和3.0版本的解除注册的方法也是相同的。
|
|
类关系图
类图引用自CodeKK的EventBus源代码分析
从类图可以看出,上部分主要是订阅相关信息,中间是EventBus,下面部分是发布者发布事件后的调用。下面开始进入源码分析:
源码分析
通过EventBus的使用流程来分析它的调用流程及实现原理。
创建EventBus
一般都是通过EventBus.getDefault()
静态方法获取到EventBus对象,先来看看getDefault()方法的实现
|
|
这里使用单例模式获取EventBus对象,目的是保证getDefault方法得到的是同一个EventBus对象。第一次创建实例,会调用EventBus的构造方法
|
|
注册事件过程
register()方法的实现
|
|
通过subscriberMethodFinder.findSubscriberMethods(subscriberClass)方法可以返回一个SubscriberMethod对象的集合,下面来看看findSubscriberMethods()方法的实现
SubscriberMethodFinder的实现
SubscriberMethodFinder类就是用来查找和缓存订阅者响应方法的信息的类。那么怎么能获得订阅者响应函数的相关信息呢?在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe注解并解析,然后生成java类来保存所有订阅者关于订阅的信息,这样就比在运行时使用反射来获得这些订阅者的信息速度要快。我们可以参考EventBus项目里的EventBusPerformance这个例子,编译后我们可以在build文件夹里找到这个类,MyEventBusIndex类,当然类名是可以自定义的,下面看一下生成的MyEventBusIndex类的组成:
|
|
可以看出是使用一个静态HashMap即:SUBSCRIBER_INDEX来保存订阅类的信息,其中包括了订阅类的Class对象,是否需要检查父类,以及订阅方法的信息SubscriberMethodInfo的数组,SubscriberMethodInfo中又保存了订阅方法的方法名、订阅的事件类型、触发线程,是否接收sticky事件以及优先级priority。这其中就保存了register()的所有需要的信息,如果再配置EventBus的时候通过EventBuilder配置:eventBus=EventBus.builder().addIndex(new MyEventBusIndex()).build()
;来将编译生成的MyEventBusIndex配置进去,这样能在SubscriberMethodFinder类中直接查找出订阅类的信息,就不需要再利用注解判断了,这种方法是作为EventBus的可选配置存在的。
SubscriberMethodFinder同样提供了通过注解来获得订阅类信息的方法,下面来看看findSubscriberMethods()到底是如何实现的:
|
|
findUsingInfo()方法就是通过查找MyEventBusIndex类中的信息来转换成List
下面来看看findUsingReflection()的实现过程
|
|
这里通过FindState类来做订阅方法的校验和保存,并通过FIND_STATE_POOL静态数组来保存FindState对象,可以使FindState复用,避免重复创建过多的对象,最终是通过findUsingReflectionSingleClass()来具体获得相关订阅方法的信息的:
|
|
上面代码运行后,订阅类的所有SubscriberMethod都已经被保存了,最后在通过getMethodsAndRelease方法返回List
下面接着来看subscribe()是如何实现的
subsribe()方法的实现
|
|
一下结合一张图来理解整个注册过程:
事件分发过程
EventBus通过post方法来发送一个事件,首先看看post方法的实现过程
|
|
首先是通过currentPostingThreadState.get()方法来得到当前线程PostingThreadState的对象,为什么是说当前线程?我们来看看currentPostingThreadState的实现:
|
|
currentPostingThreadState的实现是一个包含了PostingThreadState的ThreadLocal对象。
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储线程,而这段数据是不会与其他线程共享的,其内部原理是通过生成一个它包裹的泛型对象的数组,在不同的线程会有不同的数组索引值,这样就可以做到每个线程通过get()方法获取的时候,取到的只能是自己线程所对应的数据,所以这里取到的就是每个线程的PostingThreadState状态。
接下来我们来看postingSingleEvent()方法:
|
|
从上面可知,实际上事件分发是在postSingleEventForEventType()方法里进行的,代码如下
|
|
首先从subscriptionsByEventType里获得所有订阅了这个事件的Subscription列表,然后在通过postToSubScription()方法来分发事件,在postToSubScription()通过不同的threadMode在不同的线程里invoke()订阅者的方法,ThreadMode共有四类:
PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;
MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。
下面我们来看看invokeSubscriber(subscription, event)是如何实现的,关于不同线程的Poster使用可以参考这篇文章http://kymjs.com/code/2015/12/12/01
invokeSubscriber(subscription, event)代码如下
|
|
实际上就是通过反射调用了订阅者的订阅函数并把event对象作为参数传入,至此post()流程如上述所示。整体流程图如下:
解除注册过程
解除注册只要调用unregister()方法即可,实现如下:
|
|
最终分别从typesBySubscriber和subscriptions里分别移除订阅者以及相关信息即可
设计模式
观察者模式
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。EventBus并不是标准的观察者模式的实现,但是它的整体就是一个发布/订阅框架,也拥有观察者模式的有点,比如:发布者和订阅者的解耦。
参考文章: