前言

好多分析EventBus的文章,喜欢上来就贴源码,我看了好多次总是迷迷糊糊的,这次花时间彻底整理一下EventBus,发现EventBus核心其实就是三幅图,这三幅图涉及的是三个HashMap表,弄懂这三幅图那么EventBus就懂了。

1、第一幅图(订阅者和订阅事件)

先看一段在activity中注册和反注册EventBus的代码。

 onStart{EventBus.getDefault().register(this);}@Subscribe(threadMode = ThreadMode.MAIN)public void onEvent1(Event1 event) {}@Subscribe(threadMode = ThreadMode.MAIN)public void onEvent2(Event2 event) {}onStop{EventBus.getDefault().register(this);}

看上面的代码,注册监听的是activity,称为subscriber,在activity中监听了Event1和Event2两个事件,现在在另一个位置执行了一段代码:

EventBus.getDefault().post(new Event1());

这个时候,activity中的onEvent1就会收到事件。下面引入第一幅图:

如图所示,一个Subscribe对应多个Event,Subsribe就是上面通过register方法注册的对象,比如activity。这幅图对应EventBus中一个Map结构:

private final Map<Object, List<Class<?>>> typesBySubscriber;

EventBus会在对象register时,使用反射机制,遍历对象的方法,将带有@Subscribe标签并且合法的方法加入到typesBySubscriber。typesBySubscriber是HashMap形式,key是注册的对象本身,由于一个注册的对象中可能有多个监听事件,所以value是用list保存的Event。

看下register方法中如何处理的

 public void register(Object subscriber) {Class<?> subscriberClass = subscriber.getClass();List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}// Must be called in synchronized blockprivate void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}}

上面的代码主要做两件事:1、通过反射遍历注册对象的方法,获取其中带有@Subscribe标签的方法并且放在一个列表中,最后以注册对象为key,@Subscribe的方法列表作为value放在HashMap中,就是上图的形式。

思考

1、为什么要将注册监听对象作为key,监听事件列表作为value放在HashMap中?
要弄懂一个问题,EventBus是观察者模式,上面的activity也就是subscribe是订阅者,activity中的event是订阅事件,一个订阅者可以订阅多个事件,移除一个订阅者的监听事件时,应该将其中所有的event的事件移除。
也就是说在反注册的时候,会通过Subsribe来查找到其中所有额event进行反注册。

2、第二幅图(订阅事件和订阅者)


这种表关系是event和subsciption的对应关系,比如在Android中多个activity可能会注册监听同一个event事件,所以在执行:

EventBus.getDefault().post(new Event1());

的时候所有注册监听了Event1的监听都会要会收到回调,看下subsciption的结构


subsciption中包含,订阅的事件和订阅者本身,上面中所有的event就是订阅的事件,在Android中订阅的事件代码如下:

    @Subscribe(threadMode = ThreadMode.MAIN)public void onEvent(Event event) {}

而subsriber就是订阅者比如会在activity的onstart中执行

....
EventBus.getDefault().register(this);

那么subsribe就是activity。

思考 为什么需要保存Event和subsribe对应的关系表?

这是因为一个Event可能会有被多个subsribe订阅,所以有当执行post(Event)的时候会查找到所有订阅了Event事件的subscribe并调用其中的event方法。下面看下post方法:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {postingState.event = event;postingState.subscription = subscription;boolean aborted = false;try {postToSubscription(subscription, event, postingState.isMainThread);aborted = postingState.canceled;} finally {postingState.event = null;postingState.subscription = null;postingState.canceled = false;}if (aborted) {break;}}return true;}return false;}}

post和postSticky主要都会调用到上面的方法,上面方法中subscriptionsByEventType.get(eventClass)就是通过event的类型找上面的表中找到对应的subscriptions进行通知的。

第三幅图

在看第三幅图之前思考一个问题,postSticky到底是怎么执行的?为什么先执行postSticky,后执行register还是可以监听到event事件?
先看postSticky代码:

 public void postSticky(Object event) {synchronized (stickyEvents) {stickyEvents.put(event.getClass(), event);}// Should be posted after it is putted, in case the subscriber wants to remove immediatelypost(event);}

原来执行postSticky的时候会将event.getclass和event保存起来,然后再看下subscribe代码:

       if (subscriberMethod.sticky) {if (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);

先判断注册监听的event是不是sticky的如果是就会用stickEvents表中找到stickyEvent如果如果注册的事件event和stickyEvent一样那么就会执行一次postToSubscription方法,也就是调用注册的方法执行。

EventBus线程模式

EventBus支持监听方法在不同线程中进行回调,可以通过ThreadMode来进行设置用法如下:

@Subscribe(threadMode = ThreadMode.MainThread)public void  onNewsEvent(NewsEvent event){String message = event.getMessage();mTv_message.setText(message);}

ThreadMode总共有四种方式:
1、PostThread

事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程。

2、MainThread

事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的。

3、BackgroundThread

如果事件是在UI线程中发布出来的,那么事件处理就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么事件处理直接在该子线程中执行。所有待处理事件会被加到一个队列中,由对应线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。

4、Async

事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程。

总结

1、要理解EventBus就要从register,unRegister,post,postSticky方法入手。要理解register实质上是将订阅对象(比如activity)中的每个带有subscriber的方法找出来,最后获得调用的就是这些方法。订阅对象(比如activity)是一组event方法的持有者。

2、后注册的对象中sticky方法能够收到之前的stickyEvent方法的原因是EventBus中维护了stickyEvent的hashMap表,在subsribe注册的时候就遍历其中有没有注册监听stickyEvent如果有就会执行一次回调。

EventBus缺点

1、使用的时候有定义很多event类,
2、event在注册的时候会调用反射去遍历注册对象的方法在其中找出带有@subscriber标签的方法,性能不高。
3、需要自己注册和反注册,如果忘了反注册就会导致内存泄漏

三幅图弄懂EventBus核心原理相关推荐

  1. 分别用邻接矩阵和邻接表实现图的深度优先遍历和广度优先遍历_数据结构与算法:三十张图弄懂「图的两种遍历方式」...

    原创: 进击的HelloWorld1 引言遍历是指从某个节点出发,按照一定的的搜索路线,依次访问对数据结构中的全部节点,且每个节点仅访问一次. 在二叉树基础中,介绍了对于树的遍历.树的遍历是指从根节点 ...

  2. 深度优先遍历访问的边集合_数据结构与算法: 三十张图弄懂「图的两种遍历方式」...

    1 引言 遍历是指从某个节点出发,按照一定的的搜索路线,依次访问对数据结构中的全部节点,且每个节点仅访问一次. 在二叉树基础中,介绍了对于树的遍历.树的遍历是指从根节点出发,按照一定的访问规则,依次访 ...

  3. 5分钟弄懂语音识别技术原理

    5分钟弄懂语音识别技术原理 首先,我们知道声音实际上是一种波.常见的mp3.wmv等格式都是压缩格式,必须转成非压缩的纯波形文件来处理,比如Windows PCM文件,也就是俗称的wav文件.wav文 ...

  4. 一张图弄懂java线程的状态和生命周期

    转载自 一张图弄懂java线程的状态和生命周期 上图是一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的过程. 这些状态的枚举值都定义在java.lang.Thread.State下 ...

  5. R语言ggplot2可视化:使用patchwork包将3个ggplot2可视化结果横向组合(三幅图各占比例为33.3%,加和为100%)

    R语言ggplot2可视化:使用patchwork包将3个ggplot2可视化结果横向组合(三幅图各占比例为33.3%,加和为100%) 目录

  6. R语言ggplot2可视化:使用patchwork包(直接使用加号+)将两个ggplot2可视化结果横向组合、接着再和第三个图像横向组合起来(三幅图各占比例为50%、25%、25%)

    R语言ggplot2可视化:使用patchwork包(直接使用加号+)将两个ggplot2可视化结果横向组合.接着再和第三个图像横向组合起来(三幅图各占比例为50%.25%.25%) 目录

  7. 【笔记】三张图读懂机器学习:基本概念、五大流派与九种常见算法

    文章目录 [笔记]三张图读懂机器学习:基本概念.五大流派与九种常见算法 Chapter 1: A look at Machine learning 1.What is it? 2.How does m ...

  8. 18 张图彻底弄懂 HTTPS 的原理!

    作者 | 码海  责编 | 张文 头图 | CSDN 下载自东方 IC 来源 | 码海(ID:seaofcode) 近年来各大公司对信息安全传输越来越重视,也逐步把网站升级成 HTTPS 了.那么,大 ...

  9. 一文教你弄懂Flink核心功能和原理

    文章目录 1.Flink概述 2.Flink的特性 2.1Flink核心特性 2.2Flink特点 2.3Flink关键特性 2.4Hadoop兼容性 3.Flink的优势 4.Flink核心四大基石 ...

最新文章

  1. AI芯片是如何研制的?未来的发展之路又在何处?
  2. R—计算系统发育多样性PD (Calculate Faith’s Phylogenetic Diversity)
  3. shared_ptr 用法
  4. python打卡记录去重_Python笔记记录
  5. order by居然不能直接在union子句中使用
  6. php session和cookie区别,php中session和cookie的区别是什么?
  7. LeetCode数据库 180. 连续出现的数字
  8. ea6700梅林固件
  9. Python如何实现图片显示
  10. linux qt 多点触摸,Qt 4.6 添加 Multi-touch(多点触摸)支持
  11. linux服务器安装字体,删除字体,详细步骤
  12. 三校生计算机教学计划,第十二
  13. 用Servlet实现统计网站被访问次数的功能
  14. 云南省科协第十届学术年会在曲靖开幕
  15. php mysql 柱状图,使用EChat通过php连接mysql数据库从而实现将数据转换为柱状图
  16. Java系列课程第二十二天(网络编程、正则表达式)
  17. 计算机mac地址怎么读,如何读取MAC地址
  18. android intent.action time tick,Intent.ACTION_TIME_TICK的正确用法
  19. c语言正方形和三角形面积,【c语言】计算长方形,三角形和圆形的面积,根据用户的选择求不同形状的面积。...
  20. TP-LINK路由器刷DD-WRT实现无线中继详细教程

热门文章

  1. Python3.9,寻找质数,埃拉托色尼算法
  2. 联想ideapad 330c 15Ikb换内存条注意!!!!!只有一个插槽
  3. PyQt5 登陆界面
  4. 2022年跨境电商卖家如何在Facebook上做广告【完整指南】
  5. java判断内网ip_Java判断IP地址为内网IP还是公网IP的方法
  6. Actor模型的本质:究竟是要解决什么问题
  7. python-字典列表集合字符串
  8. 『一起学AI』生成对抗网络(GAN)原理学习及实战开发
  9. mybatis 关于出现Parameter array not found. Available parameters are [collection, list]问题的解决方案
  10. win10计算机性能选项在哪,Win10性能大提升,这些设置让你的电脑直接起飞