美文网首页Android技术知识Android进阶之旅程序员
深入浅出otto——一款轻量的消息总线框架

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

作者: 追那个小女孩 | 来源:发表于2017-08-06 18:11 被阅读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框架来减少你的编码。

    相关文章

      网友评论

        本文标题:深入浅出otto——一款轻量的消息总线框架

        本文链接:https://www.haomeiwen.com/subject/cgknlxtx.html