Guava EventBus实现原理

开篇

  • EventBus是Guava的事件处理机制,是设计模式中的观察者模式的优雅实现。对于事件监听和发布订阅模式,可以使用EventBus完美的解决。这篇文章主要是想了解下EventBus底层的实现逻辑,做到使用的时候更加游刃有余。
  • EventBus的整体使用方式如下图所示,基于Event的驱动来实现Publisher和Subscriber之前的通信。



EventBus用法

public class GuavaTest {

    public static void main(String[] args) {
        // 1、定义EventBus对象
        EventBus eventBus = new EventBus();

        // 2、定义两个观察者对象
        DataObserver1 observer1 = new DataObserver1();
        DataObserver2 observer2 = new DataObserver2();

        // 3、注册两个观察者
        eventBus.register(observer1);
        eventBus.register(observer2);
        
        // 4、分发事件
        eventBus.post(123);
        eventBus.post("hello world");
    }
}

// 定义观察者一
class DataObserver1 {

    @Subscribe
    public void action(Integer msg) {
        System.out.println("DataObserver1 String msg: " + msg);
    }

    @Subscribe
    public void action(String msg) {
        System.out.println("DataObserver1 String msg: " + msg);
    }
}

// 定义观察者二
class DataObserver2 {

    @Subscribe
    public void action(Integer msg) {
        System.out.println("DataObserver2 String msg: " + msg);
    }

    @Subscribe
    public void action(String msg) {
        System.out.println("DataObserver2 String msg: " + msg);
    }
}
  • 1、EventBus的核心对象包含EventBus对象、观察者对象Observer、事件对象Event。
  • 2、观察者对象通过注解@Subscribe来定义处理事件的逻辑实现方法,该方法的参数定义了事件类型
  • 3、通过EventBus的register方法实现观察者Objserver和EventBus的关联。
  • 4、通过EventBus的post方法实现事件Event的分发。


EventBus定义

public class EventBus {

  private final String identifier;
  private final Executor executor;
  private final SubscriberExceptionHandler exceptionHandler;
  // SubscriberRegistry用来保存监听者的信息
  private final SubscriberRegistry subscribers = new SubscriberRegistry(this);

  // dispatcher使用的是PerThreadQueuedDispatcher
  private final Dispatcher dispatcher;


  public EventBus(String identifier) {
    this(
        identifier,
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        LoggingHandler.INSTANCE);
  }

  public void register(Object object) {
    subscribers.register(object);
  }
}
  • EventBus的核心变量subscribers用来保存订阅者, Dispatcher用来定义分发的方法。
  • EventBus的注册方法register就是把观察者注册到subscribers当中。


EventBus订阅过程

final class SubscriberRegistry {

  private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers =
      Maps.newConcurrentMap();

  void register(Object listener) {
    // 获取监听者内部对应的@Subscribe注解的方法并解析成事件类型维度的Multimap,
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
    
    for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<Subscriber> eventMethodsInListener = entry.getValue();
      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
      
      // 事件监听信息保存在subscribers当中,以事件类型为维度
      if (eventSubscribers == null) {
        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }

  // 解析监听者对象
  private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    // 获取监听的类型实现
    Class<?> clazz = listener.getClass();
    // 获取指定@Subscribe注解的方法
    for (Method method : getAnnotatedMethods(clazz)) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Class<?> eventType = parameterTypes[0];
      // 按照@Subscribe注解指定方法的参数为维度进行组织
      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
  }
}
  • EventBus的subscribers用来保存观察者。
  • EventBus的注册过程核心逻辑包含发现观察者解析事件方法和保存观察者。
  • findAllSubscribers解析包含@Subscribe注解的方法生成事件类型为key,Subscriber对象为value的map对象。
  • Subscriber保存了观察者对象以及执行的方法,用来分发的时候进行回调操作。


EventBus分发过程

public class EventBus {

  public void post(Object event) {
    // 获取事件的监听者
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);

    // 遍历监听者进行事件的dispatch
    if (eventSubscribers.hasNext()) {
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }

  private static final class PerThreadQueuedDispatcher extends Dispatcher {

    private final ThreadLocal<Queue<Event>> queue =
        new ThreadLocal<Queue<Event>>() {
          @Override
          protected Queue<Event> initialValue() {
            return Queues.newArrayDeque();
          }
        };

    private final ThreadLocal<Boolean> dispatching =
        new ThreadLocal<Boolean>() {
          @Override
          protected Boolean initialValue() {
            return false;
          }
        };

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      checkNotNull(subscribers);
      Queue<Event> queueForThread = queue.get();
      queueForThread.offer(new Event(event, subscribers));

      if (!dispatching.get()) {
        dispatching.set(true);
        try {
          Event nextEvent;
          while ((nextEvent = queueForThread.poll()) != null) {
            while (nextEvent.subscribers.hasNext()) {
              nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
            }
          }
        } finally {
          dispatching.remove();
          queue.remove();
        }
      }
    }
}
  • Event的分发核心以分发的事件event去查找对应的观察者进行分发,分发的过程就是遍历观察者进行分发的过程。
  • PerThreadQueuedDispatcher支持的线程维度的分发,内部通过queue做了中转进行消息的分发。
  • dispatchEvent是执行Subscriber的回调方法的真正逻辑。


EventBus通知过程

class Subscriber {

  @Weak private EventBus bus;
  @VisibleForTesting final Object target;
  private final Method method;
  private final Executor executor;

  private Subscriber(EventBus bus, Object target, Method method) {
    this.bus = bus;
    this.target = checkNotNull(target);
    this.method = method;
    method.setAccessible(true);

    this.executor = bus.executor();
  }

  final void dispatchEvent(final Object event) {
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            try {
              invokeSubscriberMethod(event);
            } catch (InvocationTargetException e) {
            }
          }
        });
  }

  @VisibleForTesting
  void invokeSubscriberMethod(Object event) throws InvocationTargetException {
    try {
      method.invoke(target, checkNotNull(event));
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    } catch (InvocationTargetException e) {
    }
  }
}
  • 回调的过程就是执行invokeSubscriberMethod方法,内部就是常见的method.invoke反射进行回调实现。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监...
    tracy_668阅读 5,848评论 0 5
  • EventBus的设计理念是基于观察者模式的,可以参考设计模式(1)—观察者模式先来了解该设计模式。 1、程序示例...
    开发者如是说阅读 847评论 0 5
  • 最近需要使用事件驱动,打算使用EventBus管理事件的注册和分发。于是仔细阅读了下Guava的EventBus实...
    小猫无痕阅读 1,678评论 0 2
  • 前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 不知不...
    hpc阅读 606评论 0 0
  • 分析一个开源项目的源码,首先先从使用的入口开始,然后Debug或者点点点即可。很多时候源码并不难, 只是被很多人分...
    Alien的小窝阅读 26,979评论 2 43