[Android事件总线]otto介绍和源码解读

概念

从监听者模式说起

在写代码的场景中,我们经常碰到“希望当发生某种情况能触发一段逻辑”的需求。拿Android举例,最常见的例子就是当某个按钮被点击时,做一段逻辑。

如果说让事件处理的一方决定触发的时机,那只能不断轮询看是否发生了点击事件来决定触发逻辑了,显然是行不通的。

while(true) {
	if (view.isClick()) {
		// do something
	}
}

Android中是通过调用这个view的setOnclickListener方法实现的。设置了listener之后,当点击事件发生时,就会调用listener中的onClick方法。这里的重点在于,触发处理事件逻辑的职责变为由事件产生方来承担。

这就是观察者模式。

作用

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

一般说观察者模式有下面几个主要概念

  • 抽象主题(Subject)角色
  • 具体主题(ConcreteSubject)角色
  • 抽象观察者(Observer)角色
  • 具体观察者(ConcreteObserver)角色

事件总线像是更进一步,所有的主题和观察者的关系由事件总线调配,当要关心一个事件的时候,只需要在bus上注册就好,产生事件后也是在bus发送。

示意图

otto、eventbus之类的库的好处在于,解耦了事件产生发送和处理的逻辑。比如在多人多模块开发中,我只需要知道我要关心某个事件,使用注解把处理逻辑和事件绑定就行。至于这个事件是哪里怎么被发送出来的,我完全不用关心。

使用

仓库地址

用gradle构建的话,只需要在build.gradle里加上

compile 'com.squareup:otto:1.3.8'

就可以了。

otto的使用十分简单。使用@Subscribe注解标记监听方法,@Producer注解使用到的场景少些,用来标记能产生被监听的事件的方法。

首先在想要监听事件的地方定义处理事件的方法,在方法上加上@Subscribe注解:

@Subscribe 
public void answerAvailable(SomeEvent event) {
    // 处理事件event
}

然后把这个类用register方法注册到事件总线上

Bus bus = new Bus();
bus.register(this);

之后,只需要在事件发生的地方,调用post方法把事件post出去,上面@subscribe注册的方法就会收到事件。当然,register和post的要是同一个bus。

bus.post(new SomeEvent());

当post了一个事件之后,上面register过的被@Subscribe标记的answerAvailable就会被调用,并且post的事件会作为参数传入。事件和监听者的匹配是otto根据发送的事件和监听方法的参数类型自动匹配的。加上调用方法的时候事件会作为参数传入,otto要求@Subscribe注解标记的方法一定是有且只有一个参数,并且是修饰符为pubic void。

除了@Subscribe注解,还有一个@Produce注解,用来标记一个产生事件的方法。

虽然otto官方已经标记为deprecated并停止维护,推荐使用RxJava和RxAndroid,不过otto大部分情况下依然可以满足需求,并且足够简单

源码解读

其实otto的代码很简单,类也不多。所有代码结构如下:

.
├── AnnotatedHandlerFinder.java  // 实际负责解析 @Produce和@Subscrib注解
├── Bus.java                     // 事件总线,提供subscribe、post、unsubscribe方法
├── DeadEvent.java               // 当发送事件没有找到注册者时会被发送,可以注册用于log
├── EventHandler.java            // 表示一个监听者
├── EventProducer.java           // 表示一个事件生产者
├── HandlerFinder.java           // 解析监听者
├── Produce.java                 // @Produce注解
├── Subscribe.java               // @Subscribe注解
└── ThreadEnforcer.java          // 线程策略

各个类名字后面的注释已经说明了用途。其实简单来说,事件总线框架需要提供的是表达监听某个事件的发生,并在事件发生后执行一段逻辑的手段。

整个框架中最重要的就是三个方法:

  • register
  • post
  • unregister

post

某个事件发生后流程从Bus.post()方法开始,然后触发处理逻辑。先来看下post方法

public void post(Object event) {
    if (event == null) {
      throw new NullPointerException("Event to post must not be null.");
    }
    enforcer.enforce(this);

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

    boolean dispatched = false;
    for (Class<?> eventType : dispatchTypes) {
      Set<EventHandler> wrappers = getHandlersForEventType(eventType);

      if (wrappers != null && !wrappers.isEmpty()) {
        dispatched = true;
        for (EventHandler wrapper : wrappers) {
          enqueueEvent(event, wrapper);
        }
      }
    }

    if (!dispatched && !(event instanceof DeadEvent)) {
      post(new DeadEvent(this, event));
    }

    dispatchQueuedEvents();
  }

判完空之后会调用Enforcer的enforce方法。post、register、unregister方法都会用Enforcer来负责检查当前线程实是否符合要求。Enforcer接口默认有两个实现,ANY任意线程,MAIN限制主线程。因为默认使用MAIN,有时候我们碰到信息为Event bus accessed from non-main thread的Exception,就是这个原因。

flattenHierarchy()方法是找到当前事件的父类,所以当post一个事件时,在这个事件的父类上注册的方法也会被调用到。

getHandlersForEventType()这一步很关键,这是根据当前事件的类型找到在这个事件类型上注册的所有方法,以便后面能调用这些方法。

Set<EventHandler> getHandlersForEventType(Class<?> type) {
    return handlersByType.get(type);
}

但是这个方法十分的简单。说明事件类型和注册方法的对应关系是之前就收集记录好了的。查看handlersByType被调用的地方,就知道这个收集工作是在register方法完成的。先继续看完post方法的流程,再回过头来看register方法。

获取当前事件的注册方法时,当发现这个事件类型没有被注册过,会发送一个deadEvent post(new DeadEvent(this, event))

post里拿到当前事件类型对应的注册方法后,调用dispatchQueuedEvents()方法把事件分发下去。最后会调用到EventHandler类的handleEvent()方法。EventHandler实际上表示的就是一个注册方法,在register中我们也会看到。

public void handleEvent(Object event) throws InvocationTargetException {
    if (!valid) {
      throw new IllegalStateException(toString() + " has been invalidated and can no longer handle events.");
    }
    try {
      method.invoke(target, event);
    } catch (IllegalAccessException e) {
      throw new AssertionError(e);
    } catch (InvocationTargetException e) {
      if (e.getCause() instanceof Error) {
        throw (Error) e.getCause();
      }
      throw e;
    }
  }

这里就是把事件作为参数通过反射调用注册方法。这样,也就完成了通知观察者的流程,走到事件处理逻辑。

register

回过头来看register方法。

前面说到,事件类型和注册方法的对应关系是在register方法中收集的,而这正是register方法的主要主要作用。

public void register(Object object) {
    if (object == null) {
      throw new NullPointerException("Object to register must not be null.");
    }
    enforcer.enforce(this);

    Map<Class<?>, EventProducer> foundProducers = handlerFinder.findAllProducers(object);
    for (Class<?> type : foundProducers.keySet()) {

      final EventProducer producer = foundProducers.get(type);
      EventProducer previousProducer = producersByType.putIfAbsent(type, producer);
      //checking if the previous producer existed
      if (previousProducer != null) {
        throw new IllegalArgumentException("Producer method for type " + type
          + " found on type " + producer.target.getClass()
          + ", but already registered by type " + previousProducer.target.getClass() + ".");
      }
      Set<EventHandler> handlers = handlersByType.get(type);
      if (handlers != null && !handlers.isEmpty()) {
        for (EventHandler handler : handlers) {
          dispatchProducerResultToHandler(handler, producer);
        }
      }
    }

    Map<Class<?>, Set<EventHandler>> foundHandlersMap = handlerFinder.findAllSubscribers(object);
    for (Class<?> type : foundHandlersMap.keySet()) {
      Set<EventHandler> handlers = handlersByType.get(type);
      if (handlers == null) {
        //concurrent put if absent
        Set<EventHandler> handlersCreation = new CopyOnWriteArraySet<EventHandler>();
        handlers = handlersByType.putIfAbsent(type, handlersCreation);
        if (handlers == null) {
            handlers = handlersCreation;
        }
      }
      final Set<EventHandler> foundHandlers = foundHandlersMap.get(type);
      if (!handlers.addAll(foundHandlers)) {
        throw new IllegalArgumentException("Object already registered.");
      }
    }

    for (Map.Entry<Class<?>, Set<EventHandler>> entry : foundHandlersMap.entrySet()) {
      Class<?> type = entry.getKey();
      EventProducer producer = producersByType.get(type);
      if (producer != null && producer.isValid()) {
        Set<EventHandler> foundHandlers = entry.getValue();
        for (EventHandler foundHandler : foundHandlers) {
          if (!producer.isValid()) {
            break;
          }
          if (foundHandler.isValid()) {
            dispatchProducerResultToHandler(foundHandler, producer);
          }
        }
      }
    }
  }

主要分为对@Produce注解的处理和对@Subscribe注解的处理两部分,最后都会走到AnnottedHandlerFinder的loadAnnotatedMethods

private static void loadAnnotatedMethods(Class<?> listenerClass,
      Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods) {
    for (Method method : listenerClass.getDeclaredMethods()) {
      // The compiler sometimes creates synthetic bridge methods as part of the
      // type erasure process. As of JDK8 these methods now include the same
      // annotations as the original declarations. They should be ignored for
      // subscribe/produce.
      if (method.isBridge()) {
        continue;
      }
      if (method.isAnnotationPresent(Subscribe.class)) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length != 1) {
          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation but requires "
              + parameterTypes.length + " arguments.  Methods must require a single argument.");
        }

        Class<?> eventType = parameterTypes[0];
        if (eventType.isInterface()) {
          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation on " + eventType
              + " which is an interface.  Subscription must be on a concrete class type.");
        }

        if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
          throw new IllegalArgumentException("Method " + method + " has @Subscribe annotation on " + eventType
              + " but is not 'public'.");
        }

        Set<Method> methods = subscriberMethods.get(eventType);
        if (methods == null) {
          methods = new HashSet<Method>();
          subscriberMethods.put(eventType, methods);
        }
        methods.add(method);
      } else if (method.isAnnotationPresent(Produce.class)) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length != 0) {
          throw new IllegalArgumentException("Method " + method + "has @Produce annotation but requires "
              + parameterTypes.length + " arguments.  Methods must require zero arguments.");
        }
        if (method.getReturnType() == Void.class) {
          throw new IllegalArgumentException("Method " + method
              + " has a return type of void.  Must declare a non-void type.");
        }

        Class<?> eventType = method.getReturnType();
        if (eventType.isInterface()) {
          throw new IllegalArgumentException("Method " + method + " has @Produce annotation on " + eventType
              + " which is an interface.  Producers must return a concrete class type.");
        }
        if (eventType.equals(Void.TYPE)) {
          throw new IllegalArgumentException("Method " + method + " has @Produce annotation but has no return type.");
        }

        if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
          throw new IllegalArgumentException("Method " + method + " has @Produce annotation on " + eventType
              + " but is not 'public'.");
        }

        if (producerMethods.containsKey(eventType)) {
          throw new IllegalArgumentException("Producer for type " + eventType + " has already been registered.");
        }
        producerMethods.put(eventType, method);
      }
    }

    PRODUCERS_CACHE.put(listenerClass, producerMethods);
    SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);
  }

方法虽然很长,其实就是遍历当前注册的对象的方法,找到被@Subscriber和@Produce注解标记的方法,并且把结果缓存了起来。

这些被找到的方法会和这个对象本身被包装成EventHandler和EventProducer,以便需要的时候反射调用。

从EventHandler的构造函数就能看出来:

EventHandler(Object target, Method method) {
  //...
}

还有一个逻辑是 ,在register中,如果当前注册监听的事件在之前有注册的producer,producer会生产事件并分发给监听者。

unregister

unregiseter比较简单,就是把当前类的所有@Subscribe注册方法反注册,后续不会再收到事件。@Producer注解的方法失效,不会再产生事件。

总结

整个流程其实就是register里根据注解收集事件类型和监听方法的对应关系,post的根据之前的记录分发反射调用监听方法,unregister反注册。

因为使用了注解来关联事件和监听者,代码写起来更加方便,也更加解耦。API足够简单,基本上register、post、unregister就可以满足需求,同时也提供了扩展功能。

注册需要在运行时遍历所有方法,并且调用监听方法是通过反射的,效率上会有所牺牲,不过一般来说,影响微乎其微。

otto的代码很简洁,但是很有效,使用起来作用也很大。所以重要的还是设计理念。

Tips

  • 要在合适的地方调用unregister,避免内存泄漏。因为bus持有注册对象是强引用的。如果注册的是Activity的话,可以在onDestroy方法中调用unregister。
  • 可以注册一个监听DeadEvent事件的方法,打印log,以发现某个发送事件没有监听者的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值