Android技术知识Android进阶之旅程序员

深入浅出otto——一款轻量的消息总线框架

2017-08-06  本文已影响459人  追那个小女孩

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框架的构成很简单,主要包含以下几个类:

  1. Bus
    otto核心类,主要方法有三:register, post, unregister,用于完成注册、消息发送和注销三个步骤。

  2. @Subscribe, @Produce

    用在业务代码中,修饰对象是方法,目的是在register(obj)的时候方便Bus找到event在obj中对应method的注解。

    1. @Subscribe表示obj的方法s注册了这个消息,方法s会被执行
    2. @Produce表示obj的方法p注册了这个消息,在subscribe方法执行之前,会执行produce方法用来往event里面塞东西
  3. HandlerFinder

    组合在Bus中,用来给bus找到注册的obj在收到消息event的时候做什么,即找到对应的Method对象,由Bus缓存在map中,到时候好直接拿来用。

  4. ThreadEnforcer

    组合在Bus中,只有一个enforce方法,主要有ThreadEnforcer.Any和ThreadEnforcer.Main两个实现类,

    1. Main用来检测Bus的三大方法是否是在主线程执行,enforce判断如果不是主线程则抛出异常。
    2. Any则没有限制,方法体为空。
  5. DeadEvent

    在Event没有接受者的时候,Bus会发出一个DeadEvent

otto原理

otto的原理我将从Bus的三个主要方法分别来讲述他们分别干了什么事情:

  1. register(Object object)

    1. threadEnforcer.enforce,确保如果是主线程的Bus是否是运行在主线程,如果不是在主线程则抛出异常

    2. handlerFinder.findAllProducers(object),将object里面用@Produce注解且满足条件的方法(主要包括返回值为Event、没有参数等条件)全部加入到producer缓存中去。
      缓存即为producersByType: ConcurrentMap<Class<?>, EventProducer> ,其中key是Event.class, value : EventProducer是对object和他的produce Method的封装。

    3. handlerFinder.findAllSubscribers(object),同第二步类似

    4. producer.produceEvent,找到object注册的所有消息对应的producers,然后把每个produce方法都逐个执行一遍。

      注意:这里是produce方法执行的唯一时间点,在post方法中不执行produce

  2. post(Object event)

    1. threadEnforcer.enforce,同1.1

    2. flattenHierarchy(event.getClass()),获取event的所有基类,也就是说event的handler和event基类的handler在后面步骤中都会被执行— 如果存在的话。

    3. 遍历2.2中获取的event类和基类,找到对应的handler,如果有则enqueueEvent入队列

    4. 如果2.3没有找到handler,则抛出DeadEvent

    5. dispatch(event, eventHandle)

      1. isDispatching.get(),isDispatching类型是ThreadLocal<Boolean>,他是用来判断当前线程的消息队列是否正在执行,如果是的话则return

        因为整个post执行过程是同步的,在执行过程a中如果有新时间进来,也会在a的while循环中消费掉,并且如果是多线程同时执行post会通过ThreadLocal保证线程之间不会有冲突,所以在这里加这个判断是合理的。

      2. while(true) {dispatch},执行掉所有应该执行的event+handler

  3. 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框架来减少你的编码。

上一篇下一篇

猜你喜欢

热点阅读