Android 事件总线OTTO使用说明和源码解析

一、Otto简单介绍

OTTO是Square推出的库,地址:https://github.com/square/otto

先来看看otto的官方介绍

An enhanced Guava-based event bus with emphasis on Android support.Otto is an
event bus designed to decouple different parts of your application while still allowing them to communicate efficiently.Forked from Guava, Otto adds unique functionality to an already refined event
bus as well as specializing it to the Android platform.

OTTO基于Guava项目的Android支持库,如果你在Android程序开发的过程中想要不同的组件之间进行有效的通信可以使用这个库。通过otto库可以。

二、Otto简单使用

1、创建一个BUS的单例。

public class AppConfig {
private static final Bus BUS = new Bus();
public static Bus getInstance() {
    return BUS;
}
}

2、在需要使用Otto的类中注册

在类创建好之后,或者需要重新注册的时候注册,一般在Activity的onCreate()或者onPause()方法中

 AppConfig.getBusInstance().register(this);

3、定义订阅方法

@Subscribe
public void onWallpaperUpdate(MyObject obj) {
    //对obj进行需要的逻辑处理
}<span style="font-family:SimSun;">
</span>

4、发送消息

AppConfig.getBusInstance().post(myobj);

5、解绑

注意在类销毁的时候或者暂时不需要再收消息的时候解绑,,一般在Activity的onDestroy()或者onResume()方法中

 AppConfig.getBusInstance().unregister(this);

三、Otto源码解析

1、整体结构

otto的源码结构非常简单,所有类都包含在 com.squareup.otto这一个包中,

不计内部类,只有9个类跟接口,分别是

AnnotatedHandlerFinder 注解解析器

Bus 总线核心类

DeadEvent 没有接收者的事件

EventHandler 事件订阅者

EventProducer 事件生产者

HandlerFinder 获取接收者生产者

Produce 生产者的注解

Subscribe 订阅者注解

ThreadEnforcer对线程进行校验

2、Bus的关键属性方法分析

(1)构造函数

  public Bus() {
    this(DEFAULT_IDENTIFIER);
  }
  public Bus(String identifier) {
    this(ThreadEnforcer.MAIN, identifier);
  }
  public Bus(ThreadEnforcer enforcer) {
    this(enforcer, DEFAULT_IDENTIFIER);
  }
  public Bus(ThreadEnforcer enforcer, String identifier) {
    this(enforcer, identifier, HandlerFinder.ANNOTATED);
  }
  Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
    this.enforcer =  enforcer;
    this.identifier = identifier;
    this.handlerFinder = handlerFinder;
  }

我们通常使用Bus()这个构造方法,在整个app中创建一个单例,这样不但节省资源,更重要的是保证消息正常到达如果不是单例,用一个Bus进行了注册,而用另外一个Bus发送消息,这样订阅的方法是无法收到消息的。

enforcer是对线程进行校验,有两个取值,一个是ThreadEnforcer.ANY,另一个是ThreadEnforcer.MAIN,默认值是ThreadEnforcer.MAIN,这样只能在主线程进行消息处理。如果在非主线程注册或者发送消息,就会抛出异常

throw new IllegalStateException("Event bus " + bus + " accessed from non-main thread " + Looper.myLooper());

这点一定要注意:Otto默认构造方法创建的Bus实例只能在主线程调用

如果要在其他线程使用,就使用ThreadEnforcer.ANY,或者自定义ThreadEnforcer。

只在主线程使用,能保证简洁,不混乱。但是我们实际使用中很多时候还是要跨线程通信的。

identifier是一个标识,在Bus的toString()方法中会用到。

HandlerFinder用来解析注册的对象,默认的实现是HandlerFinder.ANNOTATED,使用注解解析

(2)对象注册

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);
        }
      }
    }
    Map<Class<?>, Set<EventHandler>> foundHandlersMap = handlerFinder.findAllSubscribers(object);
    for (Class<?> type : foundHandlersMap.keySet()) {
      Set<EventHandler> handlers = handlersByType.get(type);
      if (handlers == null) {
        //concurrent put if absent
        Set<EventHandler> handlersCreation = new CopyOnWriteArraySet<EventHandler>();
        handlers = handlersByType.putIfAbsent(type, handlersCreation);
        if (handlers == null) {
            handlers = handlersCreation;
        }
      }
      final Set<EventHandler> foundHandlers = foundHandlersMap.get(type);
      if (!handlers.addAll(foundHandlers)) {
        throw new IllegalArgumentException("Object already registered.");
      }
    }
    for (Map.Entry<Class<?>, Set<EventHandler>> entry : foundHandlersMap.entrySet()) {
      Class<?> type = entry.getKey();
      EventProducer producer = producersByType.get(type);
      if (producer != null && producer.isValid()) {
        Set<EventHandler> foundHandlers = entry.getValue();
        for (EventHandler foundHandler : foundHandlers) {
          if (!producer.isValid()) {
            break;
          }
          if (foundHandler.isValid()) {
            dispatchProducerResultToHandler(foundHandler, producer);
          }
        }
      }
    }
  }

对象注册首先进行了非空校验,然后是线程的校验,对象不可为空,不可多次注册

在注册对象之后,会解析出对象对应的类的生产方法和订阅方法,订阅者解析的结果保存在handlersByType,生产者解析的结果保存在producersByType里,这两个属性定义如下

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>();

handlersByType的key中保存了订阅方法的入参 参数类型,vaue中保存着订阅者具体的对象和对应方法

producersByType的key中保存了生产方法的返回值参数类型,vaue中保存着生产者具体的对象和对应方法

从源码中我们可以发现,在解析订阅者之后,如果有对应的生产者,会自动调用生产方法,并自动调用一次订阅者方法。

所谓有对应生产者,就是有Produce注解的方法返回值参数类型和有Subscribe注解的方法参数相同。

这个过程涉及到以下三个方法

public void register(Object object)

private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer)

protected void dispatch(Object event, EventHandler wrapper)

(3)消息发送

 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();
  }

这里主要涉及两个属性

 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;
    }
  };

当调用  public void post(Object event)这个方法之后,首先进行线程校验,然后解析出对应的订阅者,如果有订阅者,将event放入队列中, 如果没有,就作为一个DeadEvent,对于DeadEvent注释是这样说的

* Wraps an event that was posted, but which had no subscribers and thus could not be delivered.

* <p>Subscribing a DeadEvent handler is useful for debugging or logging, as it can detect misconfigurations in a

* system‘s event distribution.

已经很明确了,就不再赘述。

Bus在分发消息之后循环从消息队列中取值,这跟android的handler消息机制很像,不过bus中的循环在消息取完之后就结束了。

protected void dispatchQueuedEvents() {
    // don't dispatch if we're already dispatching, that would allow reentrancy and out-of-order events. Instead, leave
    // the events to be dispatched after the in-progress dispatch is complete.
    if (isDispatching.get()) {
      return;
    }
    isDispatching.set(true);
    try {
      while (true) {
        EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
        if (eventWithHandler == null) {
          break;
        }

        if (eventWithHandler.handler.isValid()) {
          dispatch(eventWithHandler.event, eventWithHandler.handler);
        }
      }
    } finally {
      isDispatching.set(false);
    }
  }

消息队列使用ThreadLocal保证了队列的独立性。同时多个线程会创建多个循环,提高了效率。发送的消息很快就就可以分发。

(4)解绑操作

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();
    }

跟register类似,首先是对象非空校验,然后是线程校验,然后解绑,注册跟解绑一定要成对,没有注册不可以解绑,解绑之后不可以直接再次解绑。

解绑主要是清理工作,减少不必要的内存,防止内存泄漏。解绑之后就不能再收到绑定对象相关的消息了。

3、AnnotatedHandlerFinder对注解的解析,构建生产者和订阅者

解析出来的信息存放在PRODUCERS_CACHE,SUBSCRIBERS_CACHE中,

  /** Cache event bus producer methods for each class. */
  private static final ConcurrentMap<Class<?>, Map<Class<?>, Method>> PRODUCERS_CACHE =
    new ConcurrentHashMap<Class<?>, Map<Class<?>, Method>>();

  /** Cache event bus subscriber methods for each class. */
  private static final ConcurrentMap<Class<?>, Map<Class<?>, Set<Method>>> SUBSCRIBERS_CACHE =
    new ConcurrentHashMap<Class<?>, Map<Class<?>, Set<Method>>>();

结构相同,key值是解析对象对应的class,value是方法参数类型(生产者是返回数据类型,订阅者是方法参数的数据类型)和对应方法。

解析生产者依次调用:

static Map<Class<?>, EventProducer> findAllProducers(Object listener),

private static void loadAnnotatedProducerMethods(Class<?> listenerClass,Map<Class<?>, Method> producerMethods) ,

private static void loadAnnotatedMethods(Class<?> listenerClass,Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods)。

解析订阅者依次调用:

static Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) ,

private static void loadAnnotatedSubscriberMethods(Class<?> listenerClass,Map<Class<?>, Set<Method>> subscriberMethods) ,

private static void loadAnnotatedMethods(Class<?> listenerClassMap<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods)。

最终都是调用loadAnnotatedMethods

在解析过程中,违反一些规则会抛出异常

@Subscribe注解的方法只能有一个参数

@Subscribe注解的方法必须有具体实现,不能是个接口

@Subscribe注解的方法必须是public的

@Produce注解的方法入参必须是空的

@Produce注解的方法返回值类型不能是void

@Produce注解的方法必须是具体实现,不能是个接口

@Produce注解的方法必须是public的

@Produce注解的方法相同返回值类型的方法只能有一个

四、总结

OTTO是非常轻量级的,多数实现依赖注解反射,使用过程中如果要对代码进行混淆要特别注意。

android事件总线处理还有个很好的开源框架是EventBus,EventBus稍微重量级一些,复杂一些,对应的功能更多,对线程控制更加灵活。对EventBus的详细介绍可以参照http://blog.csdn.net/robertcpp/article/details/51546714。

用RXJAVA也可以实现事件总线,以后在做详细说明。无论用哪个框架,归根到底都是一种观察者模式。可以根据项目需要选择合适的框架。当然,通过原生的广播,Handler机制,肯定也能实现。

欢迎扫描二维码,关注公众账号

时间: 2024-07-31 03:08:33

Android 事件总线OTTO使用说明和源码解析的相关文章

Android事件总线(二)EventBus3.0源码解析

相关文章 Android事件总线(一)EventBus3.0用法全解析 前言 上一篇我们讲到了EventBus3.0的用法,这一篇我们来讲一下EventBus3.0的源码以及它的利与弊. 1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus getDefault() { if (defaultInstance == null) { synchroniz

Android退出程序(三)——Android事件总线

概述 当我们跟随时间的脚步向前挪动的时候,总能发现原来以前的自己做了一些愚蠢的事情.但,不见得以前就是不好的事情.我在以前的博客中写过两种关于Android中如何退出应用程序的方法.近日,我又发现了一个看似高大上的方法,因为作为程序员的你,使用起来会更加方便.它就像是你得心应手的工具,在需要的时候让你有一种,还好有它的感觉.下面我就使用Android事件总线来对Android退出程序作一个说明. AndroidEventBus简介 AndroidEventBus是一个Android平台的事件总线

Android事件总线分发库EventBus3.0的简单讲解与实践

Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅.以及将发送者和接收者解耦.反正能帮助我们快速开发,这个确实是个好东西,其实鸿洋大神已经对源码作了一个较全面的剖析了 Android EventBus源码解析 带你深入理解Ev

android事件总线(eventbus)设计与实现

1. 功能介绍 AndroidEventBus是一个Android平台的事件总线库, 它简化了Activity.Fragment.Service等组件或者对象之间的交互,很大程度上降低了它们之间的耦合,使得我们的代码更加简洁,耦合性更低,提升我们的代码质量. AndroidEventBus吸收了greenrobot的EventBus以及square的otto的优点,并在此基础上做出了相应的改进,使得事件总线框架更适合用户的使用习惯,也使得事件的投递更加的精准.灵活. 与EventBus.otto

Android FM模块学习之四源码解析(二)

上一章我们了解了FM主activity:FMRadio.java,若没查看的,请打开链接Android FM模块学习之四源码解析(一) 查看fmradio.java源码注释.接下来我们来看看FM重要的一个类:FMRadioService.java 由上一章我们已经知道,打开FM时,在OnStart函数中会bindToService来开启服务, public boolean bindToService(Context context, ServiceConnection callback) { L

Dubbo原理和源码解析之服务暴露

一.框架设计 在官方<Dubbo 用户指南>架构部分,给出了服务调用的整体架构和流程: 另外,在官方<Dubbo 开发指南>框架设计部分,给出了整体设计: 以及暴露服务时序图: 本文将根据以上几张图,分析服务暴露的实现原理,并进行详细的代码跟踪与解析. 二.原理和源码解析 2.1 标签解析 从文章<Dubbo原理和源码解析之标签解析>中我们知道,<dubbo:service> 标签会被解析成 ServiceBean. ServiceBean 实现了 Init

Android事件总线

Android事件总线还能怎么玩? 顾名思义,AndroidEventBus ( github链接 ,关于我为什么要写这个库请参考<AndroidEventBus ( 事件总线 ) 的设计与实现>)是一个Android平台的事件总线框架,它简化了Activity.Fragment.Service等组件之间的交互,很大程度上降低了它们之间的耦合,使我们的代码更加简洁,耦合性更低,提升了我们的代码质量.但它能做的却不仅限于这些.经过定制,它能完成很多有意思的功能,那么究竟该怎么做呢?就让我们一起往

Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用

写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试.其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其实现原理.(转载请指明出于breaksoftware的csdn博客) 应用 GTest将这两种方法叫做:Typed Tests和Type-Parameterized Tests.我觉得可能叫做简单模式.高级模式比较通用.先不管这些名字吧,我们看看怎么使用 简单模式(Typed Tests) 首先我们

Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用

在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于breaksoftware的csdn博客) EXPECT_TRUE(IsPrime(0)); EXPECT_TRUE(IsPrime(1)); EXPECT_TRUE(IsPrime(2)); ...... EXPECT_TRUE(IsPrime(9999)); 这种写法明显是不合理的.GTest框架当然