Otto使用入门

介绍

Otto 是square公司出的一个事件库(pub/sub模式),用来简化应用程序组件之间的通讯。

Otto 修改自Google的Guava库,专门为Android平台进行了优化。

使用:

首先实现一个Bus的单例

Otto本身是为Android平台专门开发的,使用的时候最好是使用单例模式。

import com.squareup.otto.Bus;
public final class BusProvider {
    private static final Bus BUS = new Bus();
    public static Bus getInstance() {
        return BUS;
    }
    private BusProvider() {
    }
}

bus对象只有作为

单例

共享的时候才足够高效,推荐使用依赖注入框架来注入单例对象或者采用类似的机制。

其次是自定义一个定义Event事件,用来封装信息发布事件

public class MessageEvent {
    public String msg;
    public MessageEvent(String msg) {
        this.msg = msg;
    }
 }

再次是MainActivity

注册事件--

onCreate

@Override
protected void onDestroy() {
    super.onDestroy();
    BusProvider.getInstance().unregister(this);
}

取消注册事件--

onDestroy

@Subscribe
public void answerAvailable(MessageEvent event) {
    // TODO: React to the event somehow!
}

一旦调用了register方法,Otto就会通过反射去寻找所有带有@Subscribe或者@Produce注解的方法,并将这些方法缓存下来。只有 在调用了register之后,该类里面标注了@Subscribe或者@Produce的方法才会在适当的时候被调用。另外,当不需要订阅事件的时候, 可以调用unregister来取消订阅。

订阅事件--

只需要在方法上加上

@Subscribe

注解,同时在适当的地方调用register

@Produce
public MessageEvent produceAnswer() {
    return new MessageEvent("42");
}

发布一个事件很简单,调用post方法就可以,post方法可以接受任何类型

@Produce
public MessageEvent produceAnswer() {
    return new MessageEvent("42");
}

生产者

有时候当订阅某个事件的时候,希望能够获取当前的一个值,比如订阅位置变化事件的时候,希望能拿到当前的位置信息。Otto中@Produce正是扮演了这么一个生产者的角色。

@Produce也是用于方法,返回值是你要订阅的事件的类型。

bus.post(new MessageEvent("42"));
//或者这样用也行
bus.post(produceAnswer());

注意subscribe方法接收的参数类型需要和post参数的类型一致或者是post参数类型的父类。

使用@Produce之后,也需要调用bus.register()。调用了register方法之后,所有之前订阅 AnswerAvailableEvent事件的方法都会被执行一次,参数就是produceAnswer方法的返回值,之后任何新的订阅了 AnswerAvailableEvent事件的方法,也都会立即调用produceAnswer方法。

线程限制

可以指定@Subscribe和@Produce标注的回调方法所运行的线程,默认是在MainThread中执行。

// 这两个方法是等价的
Bus bus1 = new Bus();
Bus bus2 = new Bus(ThreadEnforcer.MAIN);

如果不关心在哪个线程执行,可以使用ThreadEnforcer.ANY,甚至可以使用自己实现的ThreadEnforcer接口。

proguard

需要做一些额外处理,防止混淆:

-keepattributes *Annotation*
-keepclassmembers class ** {
    @com.squareup.otto.Subscribe public *;
    @com.squareup.otto.Produce public *;
}

Otto源码分析

构造函数

使用Otto通常是通过一个Provider提供一个Bus单例。

首先我们来分析一下Bus的构造函数,Bus类的构造函数最终都会调用Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder)这个构造函数。

其中enforcer用来限制执行register,unregister以及post event的线程,如果执行这些函数的线程不是enforcer指定的线程,就会抛出异常。

identifier相当于给Bus起的一个名字,在toString方法中使用。

handlerFinder是整个event bus的核心,用于在register,unregister的时候寻找所有的subscriber和producer。handlerFinder不需 要用户指定,默认使用HandlerFinder接口中定义的常量ANNOTATED,ANNOTATED本身就是HandlerFinder的匿名实 现。

注册

如果一个类对某些事件感兴趣,需要调用register方法来注册监听这些事件,监听通过在方法上使用

@Subscribe

来实现,Otto通过方法的参数来决定是否调用该方法。

register方法首先会调用

handlerFinder的findAllProducers(object)

方法去找到所有使用了

@Produce

注解的方法。findAllProducers其实是委托AnnotatedHandlerFinder.findAllProducers方法。在 AnnotatedHandlerFinder中,定义了一个静态变量SUBSCRIBERS_CACHE

private static final Map<Class<?>, Map<Class<?>, Method>> PRODUCERS_CACHE =
      new HashMap<Class<?>, Map<Class<?>, Method>>();

PRODUCERS_CACHE 的key是监听类,就是调用bus.register()的类,value本身又是一个map,这个map的key是事件的class,value是生产事件的方法。

比如下面这个例子:

public class MainActivity extends Activity {

  @Inject Bus bus;


  @Override
  public void onResume() {
    bus.register(this);
  }


  @Produce 
  public ClickEvent produceClick() {
      // TODO: React to the event somehow!
      return new ClickEvent();
  }
}

在PRODUCERS_CACHE中就会有一条记录,它的key是MainActivity.class,value对应的map中,key是ClickEvent.class,value是produceClick Method对象。

SUBSCRIBERS_CACHE:

MainActivity.class -》ClickEvent.class -》produceClick

当调用AnnotatedHandlerFinder的findAllProducers方法时,会先根据传入的对象的类型,检查是否已经被缓存到 PRODUCERS_CACHE,如果没有的话,就会调用loadAnnotatedMethods,利用反射去寻找所有使用了@Produce注解的方 法,并且将结果缓存到PRODUCERS_CACHE中。最后,会从PRODUCERS_CACHE中取出监听类的所有Produce方法,遍历这些方 法,为一个方法构建一个EventProducer对象,并将这个EventProducer对象放到一个以事件的class作为key的map中,然后 返回这个map。EventProducer类包含了Produce方法和该方法所属的对象,并且提供了调用Produce方法的功能。

回到Bus的Register方法,调用完findAllProducers方法之后,会遍历传入的监听类的Produce方法,并且根据Produce 方法的返回值类型,来检查是否已经有对应的Subscribe存在,如果有的话,就会调用Subscribe方法,并将Producer的返回值传入。

Set<EventHandler> handlers = handlersByType.get(type);
if (handlers != null && !handlers.isEmpty()) {
    for (EventHandler handler : handlers) {
    dispatchProducerResultToHandler(handler, producer);
  }
}

这里需要注意的是,Bus对象两个map类型的常量,用来缓存所有事件的Producer和Subscriber。

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

从定义我们可以看出,一种事件只能有一个Producer,却可以有多个Subscriber。

找到了所有的producers之后,就是调用handlerFinder.findAllSubscribers(object)来寻找object中 使用@Subscribe注解的方法,过程和findAllProducers类似,唯一的不同是一个事件可以有多个subscriber,因此 findAllSubscribers的返回值类型是Map<Class<?>, Set<EventHandler>>。其中EventHandler包含了subscribe方法和订阅事件的对象的信息。

找到监听类所有的subscribe方法之后,就需要查看bus中时候有和这些subscribe方法对应的producer方法,如果有的话,就会使用 调用subscribe方法。这也就是文档上说的,一旦有新的subscriber订阅了某一事件,并且该事件有对应的producer,那么 subscriber方法就会被立即调用,并且传入producer方法的返回值。

发送事件

post(Obejct event)

方法用来发送事件给所有订阅者,它接收一个Object类型的参数,说明Otto的事件可以是任意类型的对象。post方法首先会获取所有event对象的父类

Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());

然后遍历这些父类,找到他们的所有订阅者,发送事件。这表明任何订阅了event对象父类的订阅者也都会收到event事件。值得注意的是Otto使用 ThreadLocal类型来存放事件队列 ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>> eventsToDispatch,这样极大的简化了多线程模式下的开发。

取消订阅

unregister方法,做的事情和register刚好相反,从缓存中清除所有和当前监听对象相关的producers和subscribers。

与EventBus的对比

从事件订阅的处理差别来看:

1、eventbus是采用反射的方式对整个注册的类的所有方法进行扫描来完成注册;

2、otto采用了注解的方式完成注册;

3、共同的地方缓存所有注册并有可用性的检测。同时可以移除注册;

4、注册的共同点都是采用method方法进行一个集成。

在otto更多使用场景应该就是在主线程中,因为它内部没有异步线程的场景。(也许是它自身的定位不一样,它就是为了解决UI的通信机制。所以出发点就是轻量级)在代码中主要体现这一特色的地方就是在接口ThreadEnforcer以及内部的实现域ANY和MAIN。在MAIN内部有一个是否是主线程的检查,而ANY不做任何检查的事情。

EventBus在3.0以前,还需要根据四种线程模式分别对应固定接收方法,而OTTO则可以通过注解的方法自定义方法,比较方便,但是EventBus在3.0也实现了通过注解自定义方法了。而otto介绍上不管是订阅者还是发送者都需要注册事件,但是我发现现在发送者不用注册也可以发送了。

每个框架都有自己的特点,我们开发者必须明白每个框架的出发点才能更好的使用,没有哪个框架好不好的问题,只要开发者自己使用哪个舒服,哪个就是最好的。适合自己的才是最好的。

results matching ""

    No results matching ""