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进行需要的逻辑处理
}

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机制,肯定也能实现。
 
 

 

发表评论

电子邮件地址不会被公开。