基于观察者模式-----otto源码分析
一、otto简介
otto是支付公司square一个专注于android的开源项目,该项目是一个event bus模式的消息框架,是一个基于Guava的增强型事件总线。旨在将应用程序的不同部分分离,同时仍然允许它们进行高效通信。也就是用于程序各个模块之间的通信。
项目链接:https://github.com/square/otto/tree/master/otto/src/main/java/com/squareup/otto
二、otto与观察者模式
在这之前,我们需要先了解一下什么是event bus?
我们在写程序的时候,通常需要让应用程序的各个组件之间进行消息传递(通信),各个组件之间不得不发生联系。但是当项目变得越来越大越来越复杂的时候,使用各种方法进行模块间通信、模块与后台线程进行通信时,往往代码量很大,而且他们的通信也导致了各个模块之间是高度耦合的。
EventBus可以代替Android传统的Intent,Handler,Broadcast或接口函数,在Fragment,Activity,Service线程之间传递数据,执行方法。(https://baike.baidu.com/item/EventBus/20461274)
也就是说,EventBus能够让各组件间的通信变得更简单,能有效的分离事件发送方和接收方,降低组件之间的耦合度,能避免复杂和容易出错的依赖性以及生命周期问题,同时也使代码变得更简洁,提高可读性,性能也更好。
otto是基于Guava的增强型事件总线(event bus)。通过发布和订阅事件,实际是注解和反射技术而实现的观察者模式。
观察者模式是一个使用率非常高的模式,它最常用在GUI系统、订阅–发布系统。举个例子:
我们平时都会有自己感兴趣的东西,假设我们对某些电视剧很感兴趣,我们就可以向视频软件订阅这些电视剧的开播消息,当有这些电视剧快要开播了,视频软件就马上将这些消息通知到我们。在这个例子中,“我们”是观察者,“开播消息”是被观察者。被观察者一旦发生变动,就主动通知观察者。
在这个例子中,我们可以采用一些手段来管理这些开播消息,以减少组件之间的耦合。这就是观察者模式。观察者模式可以将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。比如说,观察者模式可以通过一些抽象类或者接口,来减少实体类之间的联系,以此做到解耦合。
所以otto利用观察者模式有效的减少了各组件之间的耦合度。
三、otto源码解析
otto中一共有九个.java文件:
(1)AnnotatedHandlerFinder 注解解析器:
这个类用来负责通过反射获取 通过 @Produce 注释的方法和通过 @Subscribe 注释的方法。
(2)Bus 总线核心类
(3) DeadEvent 没有接收者的事件:
包裹一个已经提交的但是由于没有订阅者而不能发出的事件。
(4)EventHandler 事件订阅者
(5)EventProducer 事件生产者
(6)HandlerFinder 获取接收者生产者:
本身是一个接口,用于发现生产者和订阅者。
(7)Produce 生产者的注解:
标记一个方法作为实例生产者,被AnnotatedHandlerFinder和Bus类使用
(8)Subscribe 订阅者注:
将方法标记作为一个事件处理方法,同Produce一样也是在AnnotatedHandlerFinder和Bus类中使用。
(9)ThreadEnforcer对线程进行校验:
本身是一个接口,其内部有一个接口方法。otto确保收到的回调总是在自己想要的线程上,默认一个实例的所有交互都是局限于主线程上的。
要分析源代码,我们从这个库是怎么使用的来入手。我们在使用的过程中,最常用也是直接使用的其实是Bus这个类。
Bus 总线核心类
Bus.java将事件调度给侦听器,并为侦听器提供注册自己的方法。总线允许组件之间的发布 - 订阅式通信,而不需要组件彼此明确地注册(因此彼此了解)。 它专门用于使用显式注册或侦听器替换传统的Android进程内事件分发。 它不是通用发布 - 订阅系统,也不是用于进程间通信。
(1)首先利用Bus()构造函数在整个app中创建一个单例,这样不但节省资源,还能保证消息正常到达。Bus的构造函数如下:
public class Bus {
public static final String DEFAULT_IDENTIFIER = "default"; /** All registered event handlers, indexed by event type. */
private final ConcurrentMap<Class<?>, Set<EventHandler>> handlersByType =
new ConcurrentHashMap<Class<?>, Set<EventHandler>>(); /** All registered event producers, index by event type. */
private final ConcurrentMap<Class<?>, EventProducer> producersByType =
new ConcurrentHashMap<Class<?>, EventProducer>(); /** Identifier used to differentiate the event bus instance. */
private final String identifier; /** Thread enforcer for register, unregister, and posting events. */
private final ThreadEnforcer enforcer; /** Used to find handler methods in register and unregister. */
private final HandlerFinder handlerFinder; /** Queues of events for the current thread to dispatch. */
private final ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>> eventsToDispatch =
new ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>() {
@Override protected ConcurrentLinkedQueue<EventWithHandler> initialValue() {
return new ConcurrentLinkedQueue<EventWithHandler>();
}
}; /** True if the current thread is currently dispatching an event. */
private final ThreadLocal<Boolean> isDispatching = new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() {
return false;
}
}; /** Creates a new Bus named "default" that enforces actions on the main thread. */
public Bus() {
this(DEFAULT_IDENTIFIER);
} /**
* Creates a new Bus with the given {@code identifier} that enforces actions on the main thread.
*
* @param identifier a brief name for this bus, for debugging purposes. Should be a valid Java identifier.
*/
public Bus(String identifier) {
this(ThreadEnforcer.MAIN, identifier);
} /**
* Creates a new Bus named "default" with the given {@code enforcer} for actions.
*
* @param enforcer Thread enforcer for register, unregister, and post actions.
*/
public Bus(ThreadEnforcer enforcer) {
this(enforcer, DEFAULT_IDENTIFIER);
} /**
* Creates a new Bus with the given {@code enforcer} for actions and the given {@code identifier}.
*
* @param enforcer Thread enforcer for register, unregister, and post actions.
* @param identifier A brief name for this bus, for debugging purposes. Should be a valid Java identifier.
*/
public Bus(ThreadEnforcer enforcer, String identifier) {
this(enforcer, identifier, HandlerFinder.ANNOTATED);
} /**
* Test constructor which allows replacing the default {@code HandlerFinder}.
*
* @param enforcer Thread enforcer for register, unregister, and post actions.
* @param identifier A brief name for this bus, for debugging purposes. Should be a valid Java identifier.
* @param handlerFinder Used to discover event handlers and producers when registering/unregistering an object.
*/
Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
this.enforcer = enforcer;
this.identifier = identifier;
this.handlerFinder = handlerFinder;
} @Override public String toString() {
return "[Bus \"" + identifier + "\"]";
}
Bus的构造函数
在构造方法中,有两个参数:
其中,ThreadEnforcer -- 这个是确保bus运行在指定的线程
HandlerFinder -- 这个是用来提前事件生产者和操作者的注册和取消注册的
(2)构造之后,需要把监听事件的组件或者发送事件的组件注册进去==>调用Bus的register方法。在类创建好之后,或者需要重新注册的时候注册,一般在Activity的onCreate()或者onPause()方法中。
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);
//checking if the previous producer existed
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);
}
}
}
需要注册所有事件处理方法方法以接收事件,以及生产者方法以提供事件。
*如果任何订阅者正在注册已经拥有生产者的方法,则会立即调用该生产者。
*如果正在注册的生产者已经拥有订阅者,则将调用事件产生方法,把事件分发给事件订阅者。
从上面的代码可以看出:首先获取所有的@Producer注释的方法,然后获取所有的@Subscribe注释的函数,最后将两者关联起来,这样,在调用post()方法时,就能实现数据传递的效果了。
在注册对象之后,会解析出对象对应的类的生产方法和订阅方法,订阅者解析的结果保存在handlersByType,生产者解析的结果保存在producersByType里对象注册首先进行了非空校验,然后是线程的校验。
resister中使用put方法,enforcer.enforce(this)这句是用来确保运行的线程的。foundProducers是用来发现所有的生产者。foundHandlersMap这个用来获取所有的订阅者。
(3)定义订阅方法
这个方法自己实现,看具体功能。
(4)发送消息
发送消息需要调用Bus的post()方法。post()分发事件到所有注册这个事件的方法中去。如果事件分发失败,会封装一个DeadEvent对象,然后重新分发,但是这个DeadEvent对象没有被处理。
2 if (event == null) {
3 throw new NullPointerException("Event to post must not be null.");
4 }
5 enforcer.enforce(this);
6
7 Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
8
9 boolean dispatched = false;
10 for (Class<?> eventType : dispatchTypes) {
11 Set<EventHandler> wrappers = getHandlersForEventType(eventType);
12
13 if (wrappers != null && !wrappers.isEmpty()) {
14 dispatched = true;
15 for (EventHandler wrapper : wrappers) {
16 enqueueEvent(event, wrapper);
17 }
18 }
19 }
20
21 if (!dispatched && !(event instanceof DeadEvent)) {
22 post(new DeadEvent(this, event));
23 }
24
25 dispatchQueuedEvents();
26 }
其中,Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass())返回这个event的所有继承关系链的所有class对象(父类class对象和自己); Set<EventHandler> wrappers = getHandlersForEventType(eventType)这句是用来获得和eventType相关的注册方法。
总的来说:当调用 public void post(Object event)这个方法之后,首先进行线程校验,然后解析出对应的订阅者,如果有订阅者,将event放入队列中, 如果没有,就作为一个DeadEvent。
Bus在分发消息之后循环从消息队列中取值,这跟android的handler消息机制很像,不过bus中的循环在消息取完之后就结束了。消息队列使用ThreadLocal保证了队列的独立性。同时多个线程会创建多个循环,提高了效率。发送的消息很快就就可以分发。
(5)解绑(注销)
解绑也就是调用注销方法unregister(listener),删除和这个listener对象相关的生产事件的方法和注册监听的方法。
代码如下:

1 public void unregister(Object object) {
2 if (object == null) {
3 throw new NullPointerException("Object to unregister must not be null.");
4 }
5 enforcer.enforce(this);
6
7 Map<Class<?>, EventProducer> producersInListener = handlerFinder.findAllProducers(object);
8 for (Map.Entry<Class<?>, EventProducer> entry : producersInListener.entrySet()) {
9 final Class<?> key = entry.getKey();
10 EventProducer producer = getProducerForEventType(key);
11 EventProducer value = entry.getValue();
12
13 if (value == null || !value.equals(producer)) {
14 throw new IllegalArgumentException(
15 "Missing event producer for an annotated method. Is " + object.getClass()
16 + " registered?");
17 }
18 producersByType.remove(key).invalidate();
19 }
20
21 Map<Class<?>, Set<EventHandler>> handlersInListener = handlerFinder.findAllSubscribers(object);
22 for (Map.Entry<Class<?>, Set<EventHandler>> entry : handlersInListener.entrySet()) {
23 Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());
24 Collection<EventHandler> eventMethodsInListener = entry.getValue();
25
26 if (currentHandlers == null || !currentHandlers.containsAll(eventMethodsInListener)) {
27 throw new IllegalArgumentException(
28 "Missing event handler for an annotated method. Is " + object.getClass()
29 + " registered?");
30 }
31
32 for (EventHandler handler : currentHandlers) {
33 if (eventMethodsInListener.contains(handler)) {
34 handler.invalidate();
35 }
36 }
37 currentHandlers.removeAll(eventMethodsInListener);
38 }
39 }
跟register类似,首先是对象非空校验,然后是线程校验,然后解绑,注册跟解绑一定要成对,没有注册不可以解绑,解绑之后不可以直接再次解绑。
四、otto使用观察者模式的优缺点
otto多数实现依赖注解反射。android事件总线处理还有个很好的开源框架是EventBus,EventBus稍微重量级一些,复杂一些,对应的功能更多,对线程控制更加灵活。
可以看到观察者模式有以下优点:
(1)观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化,这样就可以很好的应对业务变化。
(2)增强了系统的灵活性和可扩展性。
缺点:
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者和多个观察者,开发调试等内容会比较复杂,而且在Java中消息的通知默认是顺序执行的,一个观察者卡顿,会影响整体的执行效率,在这种情况下一般考虑采用异步的方式。
参考博客:https://blog.csdn.net/robertcpp/article/details/51628749?utm_source=blogxgwz11
基于观察者模式-----otto源码分析的更多相关文章
- Set存储元素为啥是唯一的(以HashSet为例源码分析)
本文版权归 远方的风lyh和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作,如有错误之处忘不吝批评指正! 说些废话 以前面试的时候会遇到有人问Set 和list的区别 这个很好答,但 ...
- 观察者模式—jdk自带源码分析
一:观察者模式简介 二:jdk实现观察者模式的源码 三:实际例子 四:观察者模式的优点和不足 五:总结 一:观察者模式简介 有时又被称为发布(publish )-订阅(Subscribe)模式.模型- ...
- docker 源码分析 一(基于1.8.2版本),docker daemon启动过程;
最近在研究golang,也学习一下比较火的开源项目docker的源代码,国内比较出名的docker源码分析是孙宏亮大牛写的一系列文章,但是基于的docker版本有点老:索性自己就git 了一下最新的代 ...
- AtomicInteger源码分析——基于CAS的乐观锁实现
AtomicInteger源码分析——基于CAS的乐观锁实现 1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时 ...
- 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)
缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...
- JDK 自带的观察者模式源码分析以及和自定义实现的取舍
前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...
- 基于vue实现一个简单的MVVM框架(源码分析)
不知不觉接触前端的时间已经过去半年了,越来越发觉对知识的学习不应该只停留在会用的层面,这在我学jQuery的一段时间后便有这样的体会. 虽然jQuery只是一个JS的代码库,只要会一些JS的基本操作学 ...
- 并发-AtomicInteger源码分析—基于CAS的乐观锁实现
AtomicInteger源码分析—基于CAS的乐观锁实现 参考: http://www.importnew.com/22078.html https://www.cnblogs.com/mantu/ ...
- Django——基于类的视图源码分析 二
源码分析 抽象类和常用视图(base.py) 这个文件包含视图的顶级抽象类(View),基于模板的工具类(TemplateResponseMixin),模板视图(TemplateView)和重定向视图 ...
随机推荐
- react --- 搭建环境
搭建react开发环境的准备工作 1. node.js 稳定版本 2. 安装cnpm,用cnpm代替npm 3. 用yarn替代npm yarn的安装:npm install -g yarn 搭建re ...
- java字符串对象存储机制
String s1="abc";创建了几个String对象 ? String s2 = new String("abc");创建了几个String对象? s1= ...
- List、Set、Map的区别
(图一) 1.面试题:你说说collection里面有什么子类. (其实面试的时候听到这个问题的时候,你要知道,面试官是想考察List,Set) 正如图一,list和set是实现了collection ...
- SAP如何修改表的数据
修改表: 事务代码:se16n 输入表名字 输入 /h 进入维护模式 修改 GD-EDIT 和 GD-SAPEDIT 内容为大写X. se ...
- ARDUINO入门按键通信试验
1.1按键实验 1.需要学习的知识: 1) Arduino 的输入口配置方法,配置函数的用法 通过pinMode()函数,可以将ADUINO的引脚配置(INPUT)输入模式 2) 搞懂什么是抖动 机械 ...
- Ubuntu如何启用root用户登录
默认安装Ubuntu都是不允许以root用户进行登录的,想要以root用户进行登录需要进行一些操作,主要是以下几个步骤: 第一步 在终端输入命令:sudo passwd root 以普通用户登录系统, ...
- yii 动态增加路由规则
使用 UrlManager类的addRules方法. 如 $url->addRules([ 'zzz/<id:\d+>' => '/test/hi', ]); 这个规则必须在开 ...
- vue--transition-group
1.为什么要使用<transition-group> <transition></transition>是vue封装的过渡组件 <transition nam ...
- 闽南师范大学·大英A3复习专题
精读<新视野·读写教程A3> U2: words: TEXT A TEXT B appraise vt.评定 | abort vt.(因困难或危险)使(活动)终止 paralyze v ...
- 计算Java对象内存大小
摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍,涉及内存模型.锁原理 ...