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介绍上不管是订阅者还是发送者都需要注册事件,但是我发现现在发送者不用注册也可以发送了。
每个框架都有自己的特点,我们开发者必须明白每个框架的出发点才能更好的使用,没有哪个框架好不好的问题,只要开发者自己使用哪个舒服,哪个就是最好的。适合自己的才是最好的。