一、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()方法中。

 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);
      //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对象没有被处理。

 1 public void post(Object event) {
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源码分析的更多相关文章

  1. Set存储元素为啥是唯一的(以HashSet为例源码分析)

    本文版权归 远方的风lyh和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作,如有错误之处忘不吝批评指正! 说些废话 以前面试的时候会遇到有人问Set 和list的区别 这个很好答,但 ...

  2. 观察者模式—jdk自带源码分析

    一:观察者模式简介 二:jdk实现观察者模式的源码 三:实际例子 四:观察者模式的优点和不足 五:总结 一:观察者模式简介 有时又被称为发布(publish )-订阅(Subscribe)模式.模型- ...

  3. docker 源码分析 一(基于1.8.2版本),docker daemon启动过程;

    最近在研究golang,也学习一下比较火的开源项目docker的源代码,国内比较出名的docker源码分析是孙宏亮大牛写的一系列文章,但是基于的docker版本有点老:索性自己就git 了一下最新的代 ...

  4. AtomicInteger源码分析——基于CAS的乐观锁实现

    AtomicInteger源码分析——基于CAS的乐观锁实现 1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时 ...

  5. 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)

    缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...

  6. JDK 自带的观察者模式源码分析以及和自定义实现的取舍

    前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...

  7. 基于vue实现一个简单的MVVM框架(源码分析)

    不知不觉接触前端的时间已经过去半年了,越来越发觉对知识的学习不应该只停留在会用的层面,这在我学jQuery的一段时间后便有这样的体会. 虽然jQuery只是一个JS的代码库,只要会一些JS的基本操作学 ...

  8. 并发-AtomicInteger源码分析—基于CAS的乐观锁实现

    AtomicInteger源码分析—基于CAS的乐观锁实现 参考: http://www.importnew.com/22078.html https://www.cnblogs.com/mantu/ ...

  9. Django——基于类的视图源码分析 二

    源码分析 抽象类和常用视图(base.py) 这个文件包含视图的顶级抽象类(View),基于模板的工具类(TemplateResponseMixin),模板视图(TemplateView)和重定向视图 ...

随机推荐

  1. Sql语法注意事项

    #分组 group by 作用:group by 子句可以将结果集按照指定的字段值一样的记录进行分组,配合聚合函数 可以进行组内统计的工作. 注意1:当在select中时,查询的内容中如果包含聚合函数 ...

  2. Exp2 后门原理与实践 20164303 景圣

    Exp2 后门原理与实践 一.基础问题回答: 1.例举你能想到的一个后门进入到你系统中的可能方式? 答:在网上点击不安全的网页或链接. 2.例举你知道的后门如何启动起来(win及linux)的方式? ...

  3. 软件测试3gkd

        一.单元测试的任务 单元测试主要是对软件的基本组成单元进行测试,且所测试单元与程序的其他部分在测试中相隔离. 在单元测试中,我们需要对与程序构建中的单位测试以保证其可靠运行与代码规范. 单元测 ...

  4. Hello2 source analysis

    在example目录下的web\servlet\hello2\src\main\java\javaeetutorial\hello2路径里可以找到hello2的GreetingServlet.java ...

  5. git忽略对已入库文件的修改

    项目开发过程中,会遇到本地配置文件每个开发人员不同的情况,但如果遇到类似数据库配置这种最终需要加入 git 版本控制的配置,则会陷入两难境地.要么不跟踪,要么有人提交后其他人同步下来必须手动修改,非常 ...

  6. MyBatis-plus使用

    https://blog.csdn.net/qq_32867467/article/details/82944674 官网: https://mp.baomidou.com/guide/optimis ...

  7. hdu2844 Coins -----多重背包+二进制优化

    题目意思:给出你n种硬币的面额和数量,询问它能够组合成1~m元中的几种情况. 这题如果直接按照完全背包来写的话,会因为每一种硬币的数目1 ≤ Ci ≤ 1000而超时,所以这里需要运用二进制优化来解决 ...

  8. HDU 1560 DNA sequence(DNA序列)

    HDU 1560 DNA sequence(DNA序列) Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K  ...

  9. 线段树(segment_tree)

    线段树之——区间修改区间查询 1.概述 线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,基本能保证每个操作的复杂度为O(lgN). 线段树是 ...

  10. 他山之石,calling by share——python中既不是传址也不是传值

    事情是这样的,Python里是传址还是传值令人疑惑,限于本人没有C基础,所以对大家的各类水平层次不一的解答难以确信. 第一个阶段: 在读<python基础教程第二版>的时候感到疑惑,然后群 ...