Android事件总线(四)源码解析otto
前言
上一篇文章中讲到了otto的用法,这一篇我们来讲一下otto的源码。可能有人觉得otto过时了,但是通过源码我们学习的是高手设计otto时的设计理念,这种设计理念是不过时的。
otto各个类的作用
首先先来看一下otto的源码的各个类的作用,如下图所示。
如图所示,otto的源码并不多,主要的类的功能如下:
- Produce、Subscribe:发布者和订阅者注解类。
- Bus:事件总线类,用来注册和取消注册,维护发布-订阅模型,并处理事件调度分发。
- HandlerFinder、AnnotatedHandlerFinder:用来查找发布者和订阅者。
- EventProducer、EventHandler:分别封装发布者和订阅者的数据结构。
otto构造函数
在使用otto时,首先要创建Bus类,Bus类的构造函数如下所示。
public Bus() {
this(DEFAULT_IDENTIFIER);
}
这个DEFAULT_IDENTIFIER是一个字符串”default”,this调用了Bus类的另一个构造函数:
public Bus(String identifier) {
this(ThreadEnforcer.MAIN, identifier);
}
ThreadEnforcer.MAIN意味着默认在主线程中调度事件,再往里看this又调用了什么,如下所示。
public Bus(ThreadEnforcer enforcer, String identifier) {
this(enforcer, identifier, HandlerFinder.ANNOTATED);
}
第一个参数我们提到了,就是事件调度的简称,identifier为Bus的名称,默认为”default”。而identifier则是HandlerFinder,用于在register、unregister的时候寻找所有的subscriber和producer。再往里查看this又调用了什么,如下所示。
Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
this.enforcer = enforcer;
this.identifier = identifier;
this.handlerFinder = handlerFinder;
}
这个是最终调用的Bus的构造函数,在这里要首先记住handlerFinder 指的就是传进来的HandlerFinder.ANNOTATED,后面在注册时会用到handlerFinder这个属性。
注册
生成bus类后,我们要调用它的register方法来进行注册。register方法如下所示。
public void register(Object object) {
if (object == null) {
throw new NullPointerException("Object to register must not be null.");
}
enforcer.enforce(this);
Map<Class<?>, EventProducer> foundProducers = handlerFinder.findAllProducers(object);//
...
}
注释1出调用了handlerFinder的findAllProducers方法,此前讲到构造函数时,我们知道这个handlerFinder指的是HandlerFinder.ANNOTATED,ANNOTATED的代码如下所示。
HandlerFinder ANNOTATED = new HandlerFinder() {
@Override
public Map<Class<?>, EventProducer> findAllProducers(Object listener) {
return AnnotatedHandlerFinder.findAllProducers(listener);
}
@Override
public Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
return AnnotatedHandlerFinder.findAllSubscribers(listener);
}
};
从上面的代码的findAllProducers方法和findAllSubscribers方法的返回值可以推断出一个注册类只能有一个发布者,却可以有多个订阅者。findAllProducers方法最终调用的是AnnotatedHandlerFinder的findAllProducers方法:
static Map<Class<?>, EventProducer> findAllProducers(Object listener) {
final Class<?> listenerClass = listener.getClass();
Map<Class<?>, EventProducer> handlersInMethod = new HashMap<Class<?>, EventProducer>();
Map<Class<?>, Method> methods = PRODUCERS_CACHE.get(listenerClass);//
if (null == methods) {
methods = new HashMap<Class<?>, Method>();
loadAnnotatedProducerMethods(listenerClass, methods);//
}
if (!methods.isEmpty()) {
for (Map.Entry<Class<?>, Method> e : methods.entrySet()) {//
EventProducer producer = new EventProducer(listener, e.getValue());
handlersInMethod.put(e.getKey(), producer);
}
}
return handlersInMethod;
}
PRODUCERS_CACHE是一个ConcurrentHashMap,它的key为bus.register()时传入的class,而value是一个map,这个map的key是事件的class,value是生产事件的方法。注释1处首先在PRODUCERS_CACHE根据传入的对象的类型查找是否有缓存的事件方法,如果没有就调用注释2处的代码利用反射去寻找所有使用了@Produce注解的方法,并且将结果缓存到PRODUCERS_CACHE中。接着在注释3处遍历这些事件方法,并为每个事件方法创建了EventProducer类,并将这些EventProducer类作为value存入handlersInMethod并返回。接下来我们返回register方法。如下所示。
public void register(Object object) {
if (object == null) {
throw new NullPointerException("Object to register must not be null.");
}
enforcer.enforce(this);
Map<Class<?>, EventProducer> foundProducers = handlerFinder.findAllProducers(object);
for (Class<?> type : foundProducers.keySet()) {
final EventProducer producer = foundProducers.get(type);
EventProducer previousProducer = producersByType.putIfAbsent(type, producer);//
if (previousProducer != null) {
throw new IllegalArgumentException("Producer method for type " + type
+ " found on type " + producer.target.getClass()
+ ", but already registered by type " + previousProducer.target.getClass() + ".");
}
Set<EventHandler> handlers = handlersByType.get(type);
if (handlers != null && !handlers.isEmpty()) {
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);//
}
}
}
...
}
调用完findAllProducers方法后,会在注释1处检查是否有该类型的发布者已经存在,如果存在则抛出异常,不存在则调用注释2处的dispatchProducerResultToHandler方法来触发和发布者对应的订阅者来处理事件。接下来register方法的后一部分代码就不帖上来了,跟此前的流程大致一样就是调用findAllSubscribers方法来查找所有使用了@Subscribe注解的方法,跟此前不同的是一个注册类可以有多个订阅者,接下来判断是否有该类型的订阅者存在,也就是判断注册类是否已经注册,如果存在则抛出异常,不存在则查找是否有和这些订阅者对应的发布者,如果有的话,就会触发对应的订阅者处理事件。
发送事件
我们会调用Bus的post方法来发送事件,它的代码如下所示。
public void post(Object event) {
if (event == null) {
throw new NullPointerException("Event to post must not be null.");
}
enforcer.enforce(this);
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());//
boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
Set<EventHandler> wrappers = getHandlersForEventType(eventType);
if (wrappers != null && !wrappers.isEmpty()) {
dispatched = true;
for (EventHandler wrapper : wrappers) {
enqueueEvent(event, wrapper);//
}
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();//
}
注释1处的flattenHierarchy方法首先会从缓存中查找传进来的event(消息事件类)的所有父类,如果没有则找到event的所有父类并存储入缓存中。接下来遍历这些父类找到它们的所有订阅者,并在注释2处将这些订阅者压入线程的事件队列中。并在注释3处调用dispatchQueuedEvents方法依次取出事件队列中的订阅者来处理相应event的事件。
取消注册
取消注册时,我们会调用Bus的unregister方法,unregister方法如下所示。
public void unregister(Object object) {
if (object == null) {
throw new NullPointerException("Object to unregister must not be null.");
}
enforcer.enforce(this);
Map<Class<?>, EventProducer> producersInListener = handlerFinder.findAllProducers(object);//
for (Map.Entry<Class<?>, EventProducer> entry : producersInListener.entrySet()) {
final Class<?> key = entry.getKey();
EventProducer producer = getProducerForEventType(key);
EventProducer value = entry.getValue();
if (value == null || !value.equals(producer)) {
throw new IllegalArgumentException(
"Missing event producer for an annotated method. Is " + object.getClass()
+ " registered?");
}
producersByType.remove(key).invalidate();//
}
...
}
取消注册分为两部分,一部分是订阅者取消注册,另一部分是发布者取消注册。这两部分的代码类似,因此,上面的代码只列出了发布者取消注册的代码。在注释1处得到所有使用@Produce注解的方法,并遍历这些方法,调用注释2处的代码从缓存中清除所有和传进来的注册类相关的发布者,来完成发布者的取消注册操作。
Android事件总线(四)源码解析otto的更多相关文章
- Android事件分发机制源码分析
Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...
- Android View 事件分发机制 源码解析 (上)
一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...
- Git8.3k星,十万字Android主流开源框架源码解析,必须盘
为什么读源码 很多人一定和我一样的感受:源码在工作中有用吗?用处大吗?很长一段时间内我也有这样的疑问,认为哪些有事没事扯源码的人就是在装,只是为了提高他们的逼格而已. 那为什么我还要读源码呢?一刚开始 ...
- Android构建工具--AAPT2源码解析(一)
一.什么是AAPT2 在Android开发过程中,我们通过Gradle命令,启动一个构建任务,最终会生成构建产物"APK"文件.常规APK的构建流程如下: (引用自Google官方 ...
- 【Android】IntentService & HandlerThread源码解析
一.前言 在学习Service的时候,我们一定会知道IntentService:官方文档不止一次强调,Service本身是运行在主线程中的(详见:[Android]Service),而主线程中是不适合 ...
- Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...
- Android HandlerThread源码解析
在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环.那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时 ...
- 史上最详细的Android消息机制源码解析
本人只是Android菜鸡一个,写技术文章只是为了总结自己最近学习到的知识,从来不敢为人师,如果里面有不正确的地方请大家尽情指出,谢谢! 606页Android最新面试题含答案,有兴趣可以点击获取. ...
- Android事件总线(二)EventBus3.0源码解析
1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...
随机推荐
- https网页加载http资源导致的页面报错及解决方案
https是当下的网站的主流趋势,甚至像苹果这样的大公司,则完全要求用户必须使用https地址. 然而对于以前http链接来说,我们往往就存在一个兼容性问题,因为你不可能一下就全部切换过去,应该在很长 ...
- Windows系统CMD常用命令大全
命令简介 cmd是command的缩写.即命令行 . 虽然随着计算机产业的发展,Windows 操作系统的应用越来越广泛,DOS 面临着被淘汰的命运,但是因为它运行安全.稳定,有的用户还在使用,所以一 ...
- 记CTC原理
CTC,Connectionist temporal classification.从字面上理解它是用来解决时序类数据的分类问题.语音识别端到端解决方案中应用的技术.主要是解决以下两个问题 解决语音输 ...
- PHP-CPP开发扩展(六)
PHP-CPP是一个用于开发PHP扩展的C++库.本节讲解在C++中PHP异常.变量.常量的实现相关知识. 异常 PHP和C++都支持异常,而PHP-CPP库这两种语言之间的异常处理是完全透明的.你在 ...
- CSS 基础:文本和字体(4)<思维导图>
这段时间利用一下间隙时间学习了CSS的基础知识,主要目的是加深对CSS的理解,虽然个人主要工作基本都是后台开发,但是个人觉得系统学习一下CSS的基础还是很有必要的.下面我学习CSS时做的思维导图(全屏 ...
- Java并发编程笔记之SimpleDateFormat源码分析
SimpleDateFormat 是 Java 提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个 SimpleDateFormat 实例对日期进行 ...
- Java设计模式学习记录-桥接模式
前言 这次介绍结构型设计模式中的第二种模式,桥接模式. 使用桥接模式的目的就是为了解耦,松散的耦合更利于扩展,但是会增加相应的代码量和设计难度. 桥接模式 桥接模式是为了将抽象化与实现化解耦,让二者可 ...
- NLog日志框架使用探究-1
目录 前言 为什么是NLog? 目的 配置 基本配置 日志等级 输出例子 目标 参数 规则 日志分发 日志收集 结语 参考文档 前言 日志是每个程序的基本模块.本文是为了探究如何通过NLog方便及记录 ...
- [51nod1514] 美妙的序列
Description 如果对于一个 \(1\sim n\) 的排列满足: 在 \(1\sim n-1\) 这些位置之后将序列断开,使得总可以从右边找一个数,使得该数不会比左边所有数都大,则称该序列是 ...
- C#编译错误 CS0009:未能打开元数据文件
C#编译错误 CS0009:未能打开元数据文件 编译错误 说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息: CS0009: 未能 ...