一、概述

EventBus是针对Android优化的发布-订阅事件总线,简化了Android组件间的通信。EventBus以其简单易懂、优雅、开销小等优点而备受欢迎。

关于EventBus的基本使用不再详述,网上的资料很多,可以自行学习,或者直接去GitHub上查看基本使用方法。

在讲解源码之前,先说一下EventBus需要关注的点 - EventBus支持的四种线程模式(ThreadMode):

示例:

@Subscribe(threadMode = ThreadMode.POSTING)public void eventBus(MyEvent myEvent) {Toast.makeText(this, "呵呵哒", Toast.LENGTH_SHORT).show();}

a)POSTING(默认):事件在哪个线程发布,就在哪个线程消费,因此要特别注意不要在UI线程进行耗时的操作,否则会ANR;

b)MAIN:事件的消费会在UI线程。因此,不宜进行耗时操作,以免引起ANR。

c)BACKGROUND:如果事件在UI线程产生,那么事件的消费会在单独的子线程中进行。否则,在同一个线程中消费。

d)ASYNC:不管是否在UI线程产生事件,都会在单独的子线程中消费事件。

另外,EventBus还支持粘性事件,即发送一个未注册的粘性事件,注册者会在完成注册之后收到这个粘性事件。

二、原理

在开始解析源码之前,还是那句话,没有一张图解决不了的问题,如果不是那就是两张图。`(*∩_∩*)′

上图是EventBus整体的运行图。事件的发布与事件的消费可能位于一个线程,也可能位于不同的线程。这取决于我们注册消费方法的时候设置的ThreadMode。

每一个线程都有一个与之关联的Queue(通过ThreadLocal办到的),事件被发布到Queue中,循环遍历Queue中的Event,并根据Event查找可以消费该事件的类(MainActivity)与方法(@Subscribe)。最终将事件交给消费方法完成一次完整的发布与消费过程。

技术关键点:Java 反射、ThreadLocal & Queue、单例模式、建造者模式

速记技巧点:EventBus的以反射开始 - 注册、以反射结束 - 事件的消费。

本文目标:解析事件的注册、消费、解注册过程。

总得来说,与之前看过的其他开源库的源码相比,EventBus的源码还是很容易品尝的(前提是你掌握了上文提到的技术关键点)。下面开始喽。

三、源码解析

3.1、构造EventBus

单例模式:

EventBus.getDefault()

无论是事件的注册、解注册、发布,我们都会用到这句代码。

 public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();}}}return defaultInstance;}

似不似很熟悉,典型的“双重校验锁”模式。

建造者模式:

public EventBus() {this(DEFAULT_BUILDER);}EventBus(EventBusBuilder builder) {logger = builder.getLogger();subscriptionsByEventType = new HashMap<>();//<eventType,CopyOnWriteArrayList<Subscription>>事件类型和与之对应的消费者集合typesBySubscriber = new HashMap<>();stickyEvents = new ConcurrentHashMap<>();mainThreadSupport = builder.getMainThreadSupport();//1mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;//2backgroundPoster = new BackgroundPoster(this);//3asyncPoster = new AsyncPoster(this);//4.从1~4是对四种ThreadMode的支持配置indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);logSubscriberExceptions = builder.logSubscriberExceptions;logNoSubscriberMessages = builder.logNoSubscriberMessages;sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;sendNoSubscriberEvent = builder.sendNoSubscriberEvent;throwSubscriberException = builder.throwSubscriberException;eventInheritance = builder.eventInheritance;executorService = builder.executorService;//线程池相关}

看到了么,在EventBus的构造函数中,使用了目前使用广泛的建造者模式。

3.1、注册

 public void register(Object subscriber) {Class<?> subscriberClass = subscriber.getClass();//1List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);//2synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);//3}}}

1:这个subscriber就是我们使用EventBus.getDefault().register(this);传入的这个this,比如MainActivity.this。

2:通过反射,查找该Subscriber中,通过@Subscribe注解的方法(消费方法)信息,将这些方法信息封装到SubscriberMethod中,封装的内容包括Method对象、ThreadMode、事件类型、优先级、是否粘性等。一个Subscriber可能对应多个事件消费方法,因此他们的关系是1:N(其中N大于等于1)。

3、完成最终的注册过程。

   private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType;Subscription newSubscription = new Subscription(subscriber, subscriberMethod);//1CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);//2if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions);//3} else {if (subscriptions.contains(newSubscription)) {//4throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}int size = subscriptions.size();for (int i = 0; i <= size; i++) {if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {subscriptions.add(i, newSubscription);//5break;}}List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);if (subscriberMethod.sticky) {//6if (eventInheritance) {// Existing sticky events of all subclasses of eventType have to be considered.// Note: Iterating over all events may be inefficient with lots of sticky events,// thus data structure should be changed to allow a more efficient lookup// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();for (Map.Entry<Class<?>, Object> entry : entries) {Class<?> candidateEventType = entry.getKey();if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = stickyEvents.get(eventType);checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}

1:将注册者和事件消费方法封装起来,这样做的原因是完成二者的绑定关系。

2:就像上述注册者和事件消费方法是1:N的关系。一个Event与注册者之间也是1:N的关系。因为一个Event可能会被不同的Activity注册。也就是说Event、注册者、事件消费方法的关系是:1:N:M(其中M、N均大于等于1)。

3:注册者(比如MainActivity.this)与事件消费方法(SubscriberMethod)的关系,我们封装到了Subscription(s)中了。而Event和Subscription(s)的关系,我们通过HashMap保存,key为event.class,value即Subscription(s)。

4:已注册的不能重复注册。

5:按照优先级保存Subscription。

6:粘性相关的处理。

--> 通过Event -> 可以找到Subscription(s) 【subscriber 、SubscriberMethod】

3.2、发布与消费

  public void post(Object event) {PostingThreadState postingState = currentPostingThreadState.get();//1List<Object> eventQueue = postingState.eventQueue;//2eventQueue.add(event);//3:发布if (!postingState.isPosting) {postingState.isMainThread = isMainThread();postingState.isPosting = true;if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0), postingState);//4:关键方法}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}}

1:currentPostingThreadState是什么?我们先看下定义:

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};

看到ThreadLocal了没有,这是实现线程相关(线程隔离)的关键。也就说会为每一个线程生成一个PostingThreadState,这个PostingThreadState是什么?

 final static class PostingThreadState {final List<Object> eventQueue = new ArrayList<>();boolean isPosting;boolean isMainThread;Subscription subscription;Object event;boolean canceled;}

eventQueue就是保存发布到当前线程的Event的。

2|3:事件的发布。

4:这个方法是关键的核心方法,用于消费事件。此处代码最终会调用如下代码:

 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING:invokeSubscriber(subscription, event);break;case MAIN:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED:if (mainThreadPoster != null) {mainThreadPoster.enqueue(subscription, event);} else {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(subscription, event);}break;case BACKGROUND:if (isMainThread) {backgroundPoster.enqueue(subscription, event);} else {invokeSubscriber(subscription, event);}break;case ASYNC:asyncPoster.enqueue(subscription, event);break;default:throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);}}

看到没有,线程模式(ThreadMode)在此处发挥了作用,根据ThreadMode方式的不同,有不同的处理策略。以默认的POSTING模式为例,其调用了invokeSubscriber方法:

 void invokeSubscriber(Subscription subscription, Object event) {try {subscription.subscriberMethod.method.invoke(subscription.subscriber, event);} catch (InvocationTargetException e) {handleSubscriberException(subscription, event, e.getCause());} catch (IllegalAccessException e) {throw new IllegalStateException("Unexpected exception", e);}}

有没有发现最终通过什么原理完成方法调用的?反射。

 subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

这句代码是最精髓的一行了。通过这一行可以发现他们之间的关系:

event -> subscription(subscriber + subscribermethod) ->subscribermethod ->method

4、总结

EventBus原理与源码解析相关推荐

  1. 【特征匹配】ORB原理与源码解析

    相关 : Fast原理与源码解析 Brief描述子原理与源码解析 Harris原理与源码解析 http://blog.csdn.net/luoshixian099/article/details/48 ...

  2. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  3. PCA-SIFT原理及源码解析

    相关: SIFT原理与源码解析 SURF原理与源码解析 ORB原理与源码解析 FAST原理与源码解析 BRIEF描述子原理与源码解析 Harris原理与源码解析 转载请注明出处:http://blog ...

  4. Spring Boot 核心原理与源码解析 - 目录

    准备重新写 SpringBoot 配置文件解析原理 , 先在这里把要写的内容记下来 Spring Boot 核心原理与源码解析 - 目录 1\何时解析\如何解析 application.propert ...

  5. 【特征匹配】BRIEF特征描述子原理及源码解析

    相关:Fast原理及源码解析 Harris原理及源码解析 SIFT原理及源码解析 SURF原理及源码解析 转载请注明出处: http://blog.csdn.net/luoshixian099/art ...

  6. 视频教程-YOLOv3目标检测:原理与源码解析-计算机视觉

    YOLOv3目标检测:原理与源码解析 大学教授,美国归国博士.博士生导师:人工智能公司专家顾问:长期从事人工智能.物联网.大数据研究:已发表学术论文100多篇,授权发明专利10多项 白勇 ¥78.00 ...

  7. SpringMVC工作原理及源码解析

    SpringMVC工作原理及源码解析 一:SpringMVC原理图 二:SpringMVC的主要组件 1.前端控制器DispatcherServlet: 2.处理器映射器HandlerMapping: ...

  8. Mybatis运行原理及源码解析

    Mybatis源码解析 一.前言 本文旨在mybatis源码解析,将整个mybatis运行原理讲解清楚,本文代码地址: https://github.com/lchpersonal/mybatis-l ...

  9. Dubbo 实现原理与源码解析系列 —— 精品合集

    摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.[芋艿]精尽 Dubbo 原理与源码专栏 2.[ ...

最新文章

  1. 一图看懂国外智能网联汽车传感器产业发展!
  2. shell date 获取昨天日期
  3. oracle列设置标题,oracle实现某一列的值转换为列标题
  4. python的特殊类_python - 类的特殊成员方法
  5. scp复制本地文件到远程服务器,scp 本地文件到远程服务器
  6. 的环境下 qt 运行在_Ubuntu16.04环境下运行vins mono(环境配置及编译)之ROS kinetic的安装...
  7. android studio 经验
  8. sql用于字符串的聚合函数_SQL字符串函数用于数据整理(争用)
  9. Ajax之跨域访问与JSONP
  10. 地理信息系统概论_2021考研专业课地理信息系统概论(黄杏元版)知识点总结(五)...
  11. DoIP(三)—— 通信流程
  12. 微擎支持html微信支付,微信小程序云开发:现已原生支持微信支付
  13. 兄弟打印机内存已满清零方法_打印机内存已满怎么处理
  14. 雷达的工作原理示意图_雷达基本理论与基本原理
  15. 戴尔笔记本开机logo进度条时间长的解决办法
  16. java.sql.SQLException : null, message from server: “Host ‘‘ is not allowed to connect to this Maria
  17. linux下mysql可视化工具安装
  18. 关于Page Life Expectancy的一片文章
  19. html5 声控游戏,7款好玩的声控游戏,用声音征服世界!
  20. 页面静止一定时间没有操作跳转页面

热门文章

  1. 微软宣布 IE 浏览器将于 6 月 16 日正式退役,你对它有哪些回忆?
  2. Elasticsearch之 cerebro 安装配置详细使用
  3. 如何运营好微信公众号
  4. js高级面试题总结(es6)
  5. steamlit安装
  6. Android程序员必备,offer拿到手软
  7. 一、 输出 1~100 之间不能被 7 整除的数,每行输出 10 个数字,要求应用字符 串格式化方法(任何一种均可) 美化输出格式。 输出效果为:
  8. QT简单入门程序——实现可修改用户信息界面
  9. 华为云云耀云服务器 中小企业的福音
  10. 数字化转型时代,我们为什么越来越需要数据库云管平台?