深入浅出otto——一款轻量的消息总线框架
otto背景
otto是sqaure推出的一款应用主要被应用在android上的轻量级事件总线框架,目的是为了解决消息通信的问题,他通过反射帮助你在不持有对方引用的情况下通知到对方,缓解了移动开发中会遇到的耦合问题。
otto使用
otto的使用很简单,下面写一个小demo来演示otto的使用:
class TestBus {
static Bus INSTANCE;
public synchronized static Bus getInstance() {
if (INSTANCE == null) {
INSTANCE = new Bus();
}
return INSTANCE;
}
}
class TestEvent {
TestEvent() {}
}
class TestSubscriber {
@Subscribe
public void onTestEvent(TestEvent event) {
System.out.println("Get TestEvent");
}
}
class Test {
public static void main(String[] args) {
TestSubscriber subscriber = new TestSubscriber();
TestBus.getInstance().register(subscriber);
TestBus.getInstance().post(new TestEvent());
// output: Get TestEvent
TestBus.getInstance().unregister(subscriber);
TestBus.getInstance().post(new TestEvent());
// output nothing
}
}
类比较少,但是通过demo我们可以看到,Test.main并没有调用TestSubscriber.onTestEvent方法,但是onTestEvent方法确实被执行了,这就是otto框架的作用所在。
otto构成
otto框架的构成很简单,主要包含以下几个类:
-
Bus
otto核心类,主要方法有三:register, post, unregister,用于完成注册、消息发送和注销三个步骤。 -
@Subscribe, @Produce
用在业务代码中,修饰对象是方法,目的是在register(obj)的时候方便Bus找到event在obj中对应method的注解。
- @Subscribe表示obj的方法s注册了这个消息,方法s会被执行
- @Produce表示obj的方法p注册了这个消息,在subscribe方法执行之前,会执行produce方法用来往event里面塞东西
-
HandlerFinder
组合在Bus中,用来给bus找到注册的obj在收到消息event的时候做什么,即找到对应的Method对象,由Bus缓存在map中,到时候好直接拿来用。
-
ThreadEnforcer
组合在Bus中,只有一个enforce方法,主要有ThreadEnforcer.Any和ThreadEnforcer.Main两个实现类,
- Main用来检测Bus的三大方法是否是在主线程执行,enforce判断如果不是主线程则抛出异常。
- Any则没有限制,方法体为空。
-
DeadEvent
在Event没有接受者的时候,Bus会发出一个DeadEvent
otto原理
otto的原理我将从Bus的三个主要方法分别来讲述他们分别干了什么事情:
-
register(Object object)
-
threadEnforcer.enforce,确保如果是主线程的Bus是否是运行在主线程,如果不是在主线程则抛出异常
-
handlerFinder.findAllProducers(object),将object里面用@Produce注解且满足条件的方法(主要包括返回值为Event、没有参数等条件)全部加入到producer缓存中去。
缓存即为producersByType: ConcurrentMap<Class<?>, EventProducer> ,其中key是Event.class, value : EventProducer是对object和他的produce Method的封装。 -
handlerFinder.findAllSubscribers(object),同第二步类似
-
producer.produceEvent,找到object注册的所有消息对应的producers,然后把每个produce方法都逐个执行一遍。
注意:这里是produce方法执行的唯一时间点,在post方法中不执行produce
-
-
post(Object event)
-
threadEnforcer.enforce,同1.1
-
flattenHierarchy(event.getClass()),获取event的所有基类,也就是说event的handler和event基类的handler在后面步骤中都会被执行— 如果存在的话。
-
遍历2.2中获取的event类和基类,找到对应的handler,如果有则enqueueEvent入队列
-
如果2.3没有找到handler,则抛出DeadEvent
-
dispatch(event, eventHandle)
-
isDispatching.get(),isDispatching类型是ThreadLocal<Boolean>,他是用来判断当前线程的消息队列是否正在执行,如果是的话则return
因为整个post执行过程是同步的,在执行过程a中如果有新时间进来,也会在a的while循环中消费掉,并且如果是多线程同时执行post会通过ThreadLocal保证线程之间不会有冲突,所以在这里加这个判断是合理的。
-
while(true) {dispatch},执行掉所有应该执行的event+handler
-
-
-
unregister(Object object)
同register步骤类似
otto 性能
通过分析otto的执行过程发现他是用反射来做注册的,那么用反射会不会带来性能损耗呢?下面我就用一个TestCase来对比一下看看。对比的方式就是看一个onTestEvent方法直接执行和用bus.post来执行时间上有没有区别,代码如下:
public class TestOtto {
private static Bus bus = new Bus(ThreadEnforcer.ANY);
@Test
public void testOtto() {
long startTime = System.currentTimeMillis();
TestSubscriber subscriber = new TestSubscriber();
long newTime = System.currentTimeMillis();
System.out.println("new cost " + (newTime - startTime) + " mills");
TestEvent event = new TestEvent();
// for (int i = 0; i < 1000; i++) {
// bus.post(event);
// }
for (int i = 0; i < 1000; i++) {
subscriber.onTestEvent(event);
}
System.out.println("invoke end");
long endTime = System.currentTimeMillis();
System.out.println("post cost " + (endTime - startTime) + " mills");
}
public static class TestEvent {}
class TestSubscriber {
TestSubscriber() {
// bus.register(this);
}
@Subscribe
public void onTestEvent(TestEvent test) {
System.out.print("onTestEvent");
}
}
}
不使用otto执行结果:
new cost 0 mills
onTestEvent.... end
post cost 22 mills
使用otto执行结果:
new cost 24 mills
onTestEvent... invoke end
post cost 61 mills
通过对比结果可以看到,otto的性能同直接调用相比还是有差距的,特别是在注册步骤,因为要用反射遍历object的所有方法,所以时间会拉长。
otto对比
跟otto框架具备几乎相同功能的是EventBus框架,EventBus是一款跟otto框架具备相同功能,但是比otto更重的消息总线框架,otto的用法可以原模原样的搬到EventBus框架上去,连@Subscribe注解都是用的相同的名字。
说到两者的不通电,Frodo的事件总线 —— otto的bus和eventbus对比分析这篇文章列了两个框架的对比分析,概括的非常到位:
1、otto中从源码角度看,要在基类中注册事件是一件比较麻烦的事情。而Evenbus就比较友好(有网友反应如果父类中注册了总线,那么子类中必须实现一个onEvent*方法,否则程序就会崩掉。由于时间问题没进行验证这一点);
2、订阅的事件参数问题,eventbus对多参数不会抛出异常。而otto只允许接收一个参数,否则抛出RuntimeException;(其实这一点作为开源项目对代码的质量还是挺重要的)
3、从个人使用的角度来看,个人更加喜欢otto的特性。因为我只会用otto来简化UI的通信,其他的我并不需要;
4、另外,用java注解的方式来显示的标记订阅方法和生产者方法这非常的友好。至少对刚使用的开发者而言,能够清晰看到代码的思路;
5、不过对不同需求的人群来说,eventbus拓展能力和使用场景更加丰富。如果你的项目通信比较多,而且很复杂的时候;
6、eventbus定义必须onEvent开始的方法感觉还是挺别扭;
7、eventbut是不使用注解是因为注解在2.3之前的系统上会变得缓慢(这一点还需要求证一下);
8、在eventbus中有一个比较难受的地方是:在一个订阅者类中如果有两个同参数类型的接收函数,并且都要执行在主线程,那如何命名呢?由于EventBus只根据事件参数类型来判断接收函数,因此会导致两个函数都会被执行。这当然对开发者来说比较难受了,不过github上已经有人提出采用添加tag的方式来做标记扩展(AndroidEventBus)
9、使用otto时候,Bus对象只有作为单例共享的时候才足够高效。
otto总结
otto框架是一款足够轻量的消息总线框架,他的使用非常方便,但是因为他在注册的时候用到了反射,所以性能会有一定的影响,如果你的项目对性能要求并不那么高,那么完全可以使用otto框架来减少你的编码。