Android:Otto源码分析

来源:互联网 发布:淘宝账号注册首页登录 编辑:程序博客网 时间:2024/05/22 06:59

Otto源码分析

Otto是一个轻量级的EventBus,它的使用非常简单,我们使用一个Bus的单例,所有需要产生事件(@Produce bus.post(new YourEvent(…)))或者处理事件(@Subscribe)的对象,在create时register,销毁destroy时unregister即可。

使用

  • @Subscribe
    订阅事件,也就是事件的处理者,它有且仅有一个参数YourEvent,每一个Subscribe对应处理一个YourEvent。Event用于连接(匹配)post和订阅。@Subscribe使用举例:
@Subscribepublic void reveiverMethod(YourEvent event){   //...TODO }
  • @Produce
    产生事件,改方法在对象被register后即被调用(–使用情况比较特殊的),该方法必须有一个非空的返回值,参数必须为空。
  • bus.post(new YourEvent(…))
    发送一个事件,等待@Subcribe处理

使用举例

  • MainActivity
package com.example.net.mobctrl.ottotest;import com.squareup.otto.Produce;import com.squareup.otto.Subscribe;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.TextView;public class MainActivity extends Activity {    TextView tvShow;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        BusManager.getInstance().register(this);        System.out.println("debug:onCreate");        setContentView(R.layout.activity_main);        findViewById(R.id.btn_1).setOnClickListener(new OnClickListener() {            @Override            public void onClick(View arg0) {                BusManager.getInstance().post(new MyEvent("将我点击的内容,发送出去"));            }        });        tvShow = (TextView) findViewById(R.id.tv_show);    }    @Override    protected void onDestroy() {        super.onDestroy();        BusManager.getInstance().unregister(this);    }    @Subscribe    public void receiveEventByParam(MyEvent event) {        System.out.println("debug:" + event.getContent());        if (tvShow != null) {            tvShow.setText(event.getContent());        }    }    @Produce    public MyEvent sendEvent() {        return new MyEvent("这是我产生的事件(@Produce)");    }}
  • BusManager 是一个单例
package com.example.net.mobctrl.ottotest;import com.squareup.otto.Bus;public class BusManager {    private static Bus bus = null;    private BusManager() {    }    public static synchronized Bus getInstance() {        if (bus == null) {            bus = new Bus();        }        return bus;    }}
  • MyEvent 自己定义的事件类
package com.example.net.mobctrl.ottotest;public class MyEvent {    private String content;    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }    public MyEvent(){    }    public MyEvent(String content) {        super();        this.content = content;    }}

运行结果

05-20 20:41:59.923: I/System.out(30320): debug:这是我产生的事件(@Produce)05-20 20:41:59.923: I/System.out(30320): debug:onCreate05-20 20:42:11.553: I/System.out(30320): debug:将我点击的内容,发送出去

每次调用registe()方法是,会立即调用@Produce方法,将return的事件发送出去,由参数为MyEvent的@Subscribe方法接收并处理。bus.post()也是如此。

原理

主要是Bus.java里面的代码:
关键的方法有

  • public void register(Object object)
    该方法的作用是查找object里面所有带有Produce和Subscribe注解的方法,并保存在Map中,并且会立即执行Produce注解的方法。

  • public void post(Object event)
    发送事件event,根据之前注册过的object里面的方法,查找参数为event的Subscribe方法,并invoke该方法。这样就达到了post之后,调用对应Subscribe方法的目的。

  • public void unregister(Object object)
    注销object,删除掉map中保存的object的方法,释放object,防止内存泄露。

Bus源代码

具体代码如下:

package com.squareup.otto;import java.lang.reflect.InvocationTargetException;import java.util.Collection;import java.util.HashMap;import java.util.HashSet;import java.util.LinkedList;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.ConcurrentMap;import java.util.concurrent.CopyOnWriteArraySet;/* * @author Cliff Biffle * @author Jake Wharton */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 + "\"]";  }  /**   * Registers all handler methods on {@code object} to receive events and producer methods to provide events.   * <p>   * If any subscribers are registering for types which already have a producer they will be called immediately   * with the result of calling that producer.   * <p>   * If any producers are registering for types which already have subscribers, each subscriber will be called with   * the value from the result of calling the producer.   *   * @param object object whose handler methods should be registered.   * @throws NullPointerException if the object is null.   */  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);          }        }      }    }  }  private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer) {    Object event = null;    try {      event = producer.produceEvent();    } catch (InvocationTargetException e) {      throwRuntimeException("Producer " + producer + " threw an exception.", e);    }    if (event == null) {      return;    }    dispatch(event, handler);  }  /**   * Unregisters all producer and handler methods on a registered {@code object}.   *   * @param object object whose producer and handler methods should be unregistered.   * @throws IllegalArgumentException if the object was not previously registered.   * @throws NullPointerException if the object is null.   */  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();    }    Map<Class<?>, Set<EventHandler>> handlersInListener = handlerFinder.findAllSubscribers(object);    for (Map.Entry<Class<?>, Set<EventHandler>> entry : handlersInListener.entrySet()) {      Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());      Collection<EventHandler> eventMethodsInListener = entry.getValue();      if (currentHandlers == null || !currentHandlers.containsAll(eventMethodsInListener)) {        throw new IllegalArgumentException(            "Missing event handler for an annotated method. Is " + object.getClass()                + " registered?");      }      for (EventHandler handler : currentHandlers) {        if (eventMethodsInListener.contains(handler)) {          handler.invalidate();        }      }      currentHandlers.removeAll(eventMethodsInListener);    }  }  /**   * Posts an event to all registered handlers.  This method will return successfully after the event has been posted to   * all handlers, and regardless of any exceptions thrown by handlers.   *   * <p>If no handlers have been subscribed for {@code event}'s class, and {@code event} is not already a   * {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.   *   * @param event event to post.   * @throws NullPointerException if the event is null.   */  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();  }  /**   * Queue the {@code event} for dispatch during {@link #dispatchQueuedEvents()}. Events are queued in-order of   * occurrence so they can be dispatched in the same order.   */  protected void enqueueEvent(Object event, EventHandler handler) {    eventsToDispatch.get().offer(new EventWithHandler(event, handler));  }  /**   * Drain the queue of events to be dispatched. As the queue is being drained, new events may be posted to the end of   * the queue.   */  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);    }  }  /**   * Dispatches {@code event} to the handler in {@code wrapper}.  This method is an appropriate override point for   * subclasses that wish to make event delivery asynchronous.   *   * @param event event to dispatch.   * @param wrapper wrapper that will call the handler.   */  protected void dispatch(Object event, EventHandler wrapper) {    try {      wrapper.handleEvent(event);    } catch (InvocationTargetException e) {      throwRuntimeException(          "Could not dispatch event: " + event.getClass() + " to handler " + wrapper, e);    }  }  /**   * Retrieves the currently registered producer for {@code type}.  If no producer is currently registered for   * {@code type}, this method will return {@code null}.   *   * @param type type of producer to retrieve.   * @return currently registered producer, or {@code null}.   */  EventProducer getProducerForEventType(Class<?> type) {    return producersByType.get(type);  }  /**   * Retrieves a mutable set of the currently registered handlers for {@code type}.  If no handlers are currently   * registered for {@code type}, this method may either return {@code null} or an empty set.   *   * @param type type of handlers to retrieve.   * @return currently registered handlers, or {@code null}.   */  Set<EventHandler> getHandlersForEventType(Class<?> type) {    return handlersByType.get(type);  }  /**   * Flattens a class's type hierarchy into a set of Class objects.  The set will include all superclasses   * (transitively), and all interfaces implemented by these superclasses.   *   * @param concreteClass class whose type hierarchy will be retrieved.   * @return {@code concreteClass}'s complete type hierarchy, flattened and uniqued.   */  Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {    Set<Class<?>> classes = flattenHierarchyCache.get(concreteClass);    if (classes == null) {      classes = getClassesFor(concreteClass);      flattenHierarchyCache.put(concreteClass, classes);    }    return classes;  }  private Set<Class<?>> getClassesFor(Class<?> concreteClass) {    List<Class<?>> parents = new LinkedList<Class<?>>();    Set<Class<?>> classes = new HashSet<Class<?>>();    parents.add(concreteClass);    while (!parents.isEmpty()) {      Class<?> clazz = parents.remove(0);      classes.add(clazz);      Class<?> parent = clazz.getSuperclass();      if (parent != null) {        parents.add(parent);      }    }    return classes;  }  /**   * Throw a {@link RuntimeException} with given message and cause lifted from an {@link   * InvocationTargetException}. If the specified {@link InvocationTargetException} does not have a   * cause, neither will the {@link RuntimeException}.   */  private static void throwRuntimeException(String msg, InvocationTargetException e) {    Throwable cause = e.getCause();    if (cause != null) {      throw new RuntimeException(msg + ": " + cause.getMessage(), cause);    } else {      throw new RuntimeException(msg + ": " + e.getMessage(), e);    }  }  private final Map<Class<?>, Set<Class<?>>> flattenHierarchyCache =      new HashMap<Class<?>, Set<Class<?>>>();  /** Simple struct representing an event and its handler. */  static class EventWithHandler {    final Object event;    final EventHandler handler;    public EventWithHandler(Object event, EventHandler handler) {      this.event = event;      this.handler = handler;    }  }}

有趣的小工具AnnotatedHandlerFinder

当你自己写框架的时候,很多时候需要用到Annotation查找,

package com.squareup.otto;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;/** * Helper methods for finding methods annotated with {@link Produce} and {@link Subscribe}. * * @author Cliff Biffle * @author Louis Wasserman * @author Jake Wharton */final class AnnotatedHandlerFinder {  /** Cache event bus producer methods for each class. */  private static final Map<Class<?>, Map<Class<?>, Method>> PRODUCERS_CACHE =      new HashMap<Class<?>, Map<Class<?>, Method>>();  /** Cache event bus subscriber methods for each class. */  private static final Map<Class<?>, Map<Class<?>, Set<Method>>> SUBSCRIBERS_CACHE =      new HashMap<Class<?>, Map<Class<?>, Set<Method>>>();  /**   * Load all methods annotated with {@link Produce} or {@link Subscribe} into their respective caches for the   * specified class.   */  private static void loadAnnotatedMethods(Class<?> listenerClass) {    Map<Class<?>, Set<Method>> subscriberMethods = new HashMap<Class<?>, Set<Method>>();    Map<Class<?>, Method> producerMethods = new HashMap<Class<?>, Method>();    for (Method method : listenerClass.getDeclaredMethods()) {      // The compiler sometimes creates synthetic bridge methods as part of the      // type erasure process. As of JDK8 these methods now include the same      // annotations as the original declarations. They should be ignored for      // subscribe/produce.      if (method.isBridge()) {        continue;      }      if (method.isAnnotationPresent(Subscribe.class)) {        Class<?>[] parameterTypes = method.getParameterTypes();        if (parameterTypes.length != 1) {          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation but requires "              + parameterTypes.length + " arguments.  Methods must require a single argument.");        }        Class<?> eventType = parameterTypes[0];        if (eventType.isInterface()) {          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation on " + eventType              + " which is an interface.  Subscription must be on a concrete class type.");        }        if ((method.getModifiers() & Modifier.PUBLIC) == 0) {          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation on " + eventType              + " but is not 'public'.");        }        Set<Method> methods = subscriberMethods.get(eventType);        if (methods == null) {          methods = new HashSet<Method>();          subscriberMethods.put(eventType, methods);        }        methods.add(method);      } else if (method.isAnnotationPresent(Produce.class)) {        Class<?>[] parameterTypes = method.getParameterTypes();        if (parameterTypes.length != 0) {          throw new IllegalArgumentException("Method " + method + "has @Produce annotation but requires "              + parameterTypes.length + " arguments.  Methods must require zero arguments.");        }        if (method.getReturnType() == Void.class) {          throw new IllegalArgumentException("Method " + method              + " has a return type of void.  Must declare a non-void type.");        }        Class<?> eventType = method.getReturnType();        if (eventType.isInterface()) {          throw new IllegalArgumentException("Method " + method + " has @Produce annotation on " + eventType              + " which is an interface.  Producers must return a concrete class type.");        }        if (eventType.equals(Void.TYPE)) {          throw new IllegalArgumentException("Method " + method + " has @Produce annotation but has no return type.");        }        if ((method.getModifiers() & Modifier.PUBLIC) == 0) {          throw new IllegalArgumentException("Method " + method + " has @Produce annotation on " + eventType              + " but is not 'public'.");        }        if (producerMethods.containsKey(eventType)) {          throw new IllegalArgumentException("Producer for type " + eventType + " has already been registered.");        }        producerMethods.put(eventType, method);      }    }    PRODUCERS_CACHE.put(listenerClass, producerMethods);    SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);  }  /** This implementation finds all methods marked with a {@link Produce} annotation. */  static Map<Class<?>, EventProducer> findAllProducers(Object listener) {    final Class<?> listenerClass = listener.getClass();    Map<Class<?>, EventProducer> handlersInMethod = new HashMap<Class<?>, EventProducer>();    if (!PRODUCERS_CACHE.containsKey(listenerClass)) {      loadAnnotatedMethods(listenerClass);    }    Map<Class<?>, Method> methods = PRODUCERS_CACHE.get(listenerClass);    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;  }  /** This implementation finds all methods marked with a {@link Subscribe} annotation. */  static Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {    Class<?> listenerClass = listener.getClass();    Map<Class<?>, Set<EventHandler>> handlersInMethod = new HashMap<Class<?>, Set<EventHandler>>();    if (!SUBSCRIBERS_CACHE.containsKey(listenerClass)) {      loadAnnotatedMethods(listenerClass);    }    Map<Class<?>, Set<Method>> methods = SUBSCRIBERS_CACHE.get(listenerClass);    if (!methods.isEmpty()) {      for (Map.Entry<Class<?>, Set<Method>> e : methods.entrySet()) {        Set<EventHandler> handlers = new HashSet<EventHandler>();        for (Method m : e.getValue()) {          handlers.add(new EventHandler(listener, m));        }        handlersInMethod.put(e.getKey(), handlers);      }    }    return handlersInMethod;  }  private AnnotatedHandlerFinder() {    // No instances.  }}

更多交流

Android开发联盟QQ群:272209595

1 0