观察者模式
描述一对多的依赖的关系,当一个对象的状态发生变化时,所有依赖它的对象都会获得通知:
事件总线
消息或者事件流动的通道,不同的组件或者模块通过这个通道获取获取和发布消息;
事件总线涉及到4个角色:
- 订阅者,subscriber,获取指定类型的消息或者事件
- 发布者,publisher,触发或者发布消息或者事件
- 事件 Event,订阅者和发布者之间沟通的信息载体,普通的java类,只包括数据
- 事件总线,EventBus,管理订阅者和信息的存储,同时负责事件的分发和流转;
通过事件总线 组件之间相互解耦,发布者不知道具体订阅者的存在。
以上是事件总线的总体思想,如果我们设计一款EventBus,该考虑哪些设计呢?我觉得以下几个问题是比较核心的设计:
- 如何管理订阅者
- 如何存储事件
- 如何触发事件
- 如何分发事件
我看下EventBus是如何处理这些问题的(版本3.2.0),
如何管理订阅者
EventvBus的订阅者通过注解实现注册,有个两个方式:
运行时加载订阅
基本思路是在register时,通过反射获取当前class的中所有使用了Subscriber注解的方法,把他们加到事件集合里;
我们重点分析注解查找方法SubscriberMethodFinder类的findUsingReflection方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// subscriberClass 就是通过EventBus.register的对象的类
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
// 使用prepareFindState,在查找中限制类的实例个数,应该是基于性能的考虑,因为findState是存在多个map用于临时存储订阅者,订阅者所在的类及事件之间的关系
// findState就是递归过程中共享数据的变量
FindState findState = prepareFindState();
// 初始化findState
findState.initForSubscriber(subscriberClass);
// 开始递归
while (findState.clazz != null) {
// 该方法包括查找注解的核心逻辑
// 1 获取subscriberClass里的所有方法
// 2 遍历这些方法,check是否是public,是否包含1个参数
// 3 解析参数类型,解析方法名,存入findState对应的关系中
findUsingReflectionInSingleClass(findState);
// 查找父类
findState.moveToSuperclass();
}
//释放findState, 返回查找结果,SubscriberMethod列表
return getMethodsAndRelease(findState);
}这个里面有一个技巧需要注意,就是控制findState个数,以及在递归中使用。
编译时通过SubscriberIndex加载订阅
使用如例:1
2(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};基本实现是思路:使用注解生成器EventBusAnnotationProcessor编译时生成Subscribe的注册类。所以注册的整个过程由2个阶段构成,编译时和运行时。编译时负责收集所有了订阅的类及其类内的回调,运行时执行这些注册,并触发对应的回调。
EventBusAnnotationProcessor 负责解析注解类,收集并验证所有使用过Subscribe的方法和方法所在的类,基于收集到方法和类,生成Subscribe的注册类的java文件。
EventBusAnnotationProcessor 继承自AbstractProcessor类,AbstractProcessor类负责插入式注解处理器的注解处理过程,很多框架都使用这个技术,以后我会单独研究学习和大家分享,可以看这里简单了解。整体上这个类可以获取编译时环境,及注解的类型,使用注册的类型和方法,注解里使用方法等等,通过这个类我们基本上在编译时获取注解相关的所有信息;
需要注意的一点是,如果需要指定文件名和报名,可以在build.gradle 里做如下的声明:1
2
3
4
5
6javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
arguments = [eventBusIndex: 'com.demo.EventBusIndex']
}
}AbstractProcessor有很多虚方法,最重要的是process,注解的处理主要通过这个方法处理。重点分析这个方法的处理流程:
这个方法有2个输入参数,annotations,指定注解类型的集合,roundEnvironment,用于查询使用指定注解类型的类型的处理器;
获取build.gradle中指定的包名,该包名是通过注解生成类的包名;
收集使用过指定注解的所有类型,并把这些数据组织成ListMap<TypeElement, ExecutableElement> list的数据;
依据上一步的list,验证注解类型的可见性,public为可见,private和protect,检查回调方法的参数类型是否是可见类和可用类;
生成代码,主要依据list做字符串拼接,文件的路径是以声明的包名目录,比如com.demo.EventBusIndex,其文件所在的位置是,代码目录/build/generated/ap_generated_sources/版本变体/out/com/demo/EventBusIndex.java
生成的文件的大概是这样的
1 | /** This class is generated by EventBus, do not edit. */ |
其中需要仔细理解的是putIndex(SubscriberInfo info),SubscriberInfo描述了包含订阅的类,及其所有的订阅方法
同时要在Application种手动的将生成的EventBusIndex类提供给EventBus
1 | EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build(); |
以上,EventBus实现了订阅者的管理和订阅的注册