专栏首页非著名程序员EventBus3.0 使用及源码解析

EventBus3.0 使用及源码解析

叨了个叨

最近因为换工作的一些琐事搞的我一个头两个大,也没怎么去学新东西,实在是有些愧疚。新项目用到了EventBus3.0,原来只是听说EventBus的鼎鼎大名,一直没仔细研究过。趁着周末有些时间,研究下代码,也算没有虚度光阴。

EventBus GitHub : https://github.com/greenrobot/EventBus

EventBus3.0简介

EventBus是greenrobot出品的一个用于Android中事件发布/订阅的库。以前传递对象可能通过接口、广播、文件等等,尤其像同一个Activity两个Fragment之间采用接口传递对象,十分的麻烦,而且耦合度较高。使用EventBus之后,这些将不再是问题。盗用GiHub上EventBus的一张图。

可以看到,发布者(Publisher)使用post()方法将Event发送到Event Bus,而后Event Bus自动将Event发送到多个订阅者(Subcriber)。这里需要注意两个地方:(1)一个发布者可以对应多个订阅者。(2)3.0以前订阅者的订阅方法为onEvent()onEventMainThread()onEventBackgroundThread()onEventAsync()。在Event Bus3.0之后统一采用注解@Subscribe的形式,具体实现方式见下文。

EventBus3.0的使用

新建两个Activity,花3s扫一下即可。代码如下:

MainActivityonCreate()/onDestroy()中分别注册/反注册EventBus。然后写了四个测试ThreadMode的方法,调用时机注释的很清楚,就不赘述了。最后在SecondActivity的主线程和子线程中分别调用Post()方法,注意,这里Post()方法的参数为Object类型,这也就意味着我们传递任何对象都是可以的,例如JavaBeanList<E>等等都是可以的,这里为了方便演示直接传递了String。Log信息如下:

第一张图中发布者发送线程为主线程,即Post thread = 1,在订阅者收到消息时,ThreadMode = MainThreadMode = Posting的方法都在主线程调用,ASYNCBACKGROUND都新开了线程。图二对比注释同理。

除此之外,Subscribe注解还支持prioritysticky属性。priority设置接收者的优先级,默认值为0。优先级高的方法先被调用,在方法调用完成后可以调用EventBus.getDefault().cancelEventDelivery(event) ;终止优先级低的方法的调用。sticky为粘性事件,默认为关闭状态。能够收到订阅之前发送到的最后一条消息,并且发送的方法不再是post()而是postSticky()

EventBus3.0源码解析

EventBus是Very的好用。耦合度大大的降低,而且代码十分优雅。它是怎么就做到了这么优雅的呢?知其然,知其所以然。下面就开始一步步的分析。

注解标签Subscribe

对注解不了解的同学可以看下这篇博客。

注解Subscribe在运行时解析,且只能加在METHOD上。其中有三个方法,threadMode()返回类型ThreadMode为枚举类型,默认值为POSTINGsticky()默认返回false,priority()默认返回0。

1. Register流程

EventBus#getDefault()

EventBus采用双重校验锁设计为一个单例模式,奇怪的在于虽然设计为单例模式,但是构造方法确实public类型,这不是坑爹嘛!难道greenrobot在设计EventBus获取实例方法的时候在打LOL,一不小心打错了?原来啊,EventBus默认支持一条事件总线,通常是通过getDefault()方法获取EventBus实例,但也能通过直接new EventBus这种最简单的方式获取多条事件总线,彼此之间完全分开。设计之思想不禁让人拍案叫绝。

EventBus#register()

首先得到订阅者的报名.类名,即哪个具体类注册。然后调用subscriberMethodFinder.findSubscriberMethods(subscriberClass),从方法名和返回值来看,findSubscriberMethods()的作用应该是遍历查找订阅者中所有的订阅方法。

SubscriberMethodFinder#findSubscriberMethods()

注意subscriberMethods.isEmpty(),如果注册了EventBus,但却没有使用注解Subscribe是会出现EventBusException异常的。下面跟进findUsingInfo()方法。

SubscriberMethodFinder#findUsingInfo()

findState.subscriberInfo默认null,那么就进入到findUsingReflectionInSingleClass(findState),先看下这个方法,等下还要返回来看。

SubscriberMethodFinder#findUsingReflectionInSingleClass()

接下来返回SubscriberMethodFinder#findUsingInfo()接着看,在findUsingInfo()中循环执行完后return getMethodsAndRelease(findState)

getMethodsAndRelease()中将findState置空,存放进FIND_STATE_POOL数组,最后返回findState.subscriberMethods。返回EventBus#register()

EventBus#register()

调用SubscriberMethodFinder#findSubscriberMethods()后,以List<SubscriberMethod>形式返回了订阅者所有的订阅事件。然后遍历执行subscribe()方法。看样子应该是遍历List<SubscriberMethod>,然后将订阅者和订阅事件绑定。没撒好说的,跟进subscribe()

EventBus#subscribe()

EventBus#Register()其实只做了三件事:

1. 查找订阅者所有的订阅事件

2. 将订阅事件作为key,所有订阅了此订阅事件的订阅者作为value存放进subscriptionsByEventType

3. 将订阅者作为key,订阅者的所有订阅事件作为value存放进typesBySubscriber

至此,EventBus.getDefault().register(this)流程完毕。

2. Post流程

EventBus#getDefault()

获取EventBus实例。和Register流程中一样,不再赘述。

EventBus#post()

上面代码中currentPostingThreadStateThreadLocal<PostingThreadState>对象,对ThreadLocal<>机制不了解的同学,可以查看这篇博客。下面跟进postSingleEvent()方法。

EventBus#postSingleEvent()

EventBus#lookupAllEventTypes()

现在假设传递的数据为Person类,而Person类实现了IPerson接口。通过上面的分析可以得出结论:在传递对象(Person)的时候,订阅事件中参数为被传递对象的所有父类订阅事件(IPerson)也都会被调用。笔者已经验证通过,感兴趣的同学可以再验证一下。

EventBus#postSingleEventForEventType()

EventBus#register()最后总结道:将订阅事件作为key,所有订阅了此订阅事件的订阅者作为value存放进subscriptionsByEventType。这里就依据订阅事件然后查找对应所有的订阅者。注意:由于遍历订阅事件参数所有父类的原因,一个订阅事件的Post第一次执行postToSubscription()时,subscription参数,遍历时为订阅事件的订阅者。之后再调用postToSubscription()时,subscription参数都为订阅时间父类的订阅者。而event参数则一直是订阅事件中的唯一参数(最底层子类)。

EventBus#postToSubscription()

看到这里差不多可以松口气,终于要分发调用订阅者的订阅事件了!写了整整一下午,容我抽支烟再。

首先根据ThreadMode确定分发类型。这里以最常用的Main为例,其余两个Poster同理。如果是isMainThread=true,那么直接调用invokeSubscriber(),否则调用mainThreadPoster.enqueue()。下面分别解释这两种情况。

EventBus#invokeSubscriber()

没撒好说的,直接反射调用订阅者的订阅事件。注意:参数event是子类对象,就算调用订阅事件中唯一参数是参数的父类,那么传递的仍然是子类对象。笔者已经验证,感兴趣的同学可以自行验证。然后查看HandlerPoster#enqueue()

HandlerPoster#enqueue()

EventBus#invokeSubscriber()

至此,订阅者在相应线程调用订阅事件完成,EventBus.getDefault().Post()流程完毕。

EventBus#Post()也只做了三件事

1. 根据订阅事件在subscriptionsByEventType中查找相应的订阅者

2. 分发订阅者的订阅事件调用线程

2. 通过反射调用订阅者的订阅事件

3. unregister流程

EventBus#getDefault()

获取EventBus实例。和Register流程中一样,不再赘述。

EventBus#unregister()

EventBus#unsubscribeByEventType()

EventBus#register()最后总结道:

将订阅事件作为key,所有订阅了此订阅事件的订阅者作为value存放进subscriptionsByEventType

将订阅者作为key,订阅者的所有订阅事件作为value存放进typesBySubscriber

现在要反注册咯。移除相应的keyvalue即可。EventBus3.0的使用及源码解析到此结束,Have a nice day~

本文分享自微信公众号 - 非著名程序员(non-famous-coder),作者:一口仨馍

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-08-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android学习第六弹之Touch事件的处理

    在移动开发过程当中,我们经常会遇到手势处理和事件触摸的情况,如果不了解整个事件的处理机制,对于开发的同学和码农是非常痛苦的,但是事件触摸的处理确实是一个非常复杂...

    非著名程序员
  • 最能解决你的痛点问题,也是你最需要的,尽在Material Design 系列这篇

    这篇文章其实我一直在想,是写还是不写,因为关于讲 CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout,...

    非著名程序员
  • 安卓统一推送联盟,终于有消息了,统一推送时间表出炉

    可是雷声大雨点小的统一推送联盟,自曝光之后,就逐渐推出了大家的视野,每隔半年多才可能听到一次消息。但是,今天统一推送联盟在公众号上更新了最新的消息了。

    非著名程序员
  • [LeetCode Python 3] 876. Middle of the Linked List(链表的中间结点)

    设置两个指向头节点的快慢指针,快指针每次走两步,慢指针每次走一步,当快指针到达最后结点或为空时,慢指针指向的就是中间结点 。

    Woodson
  • Ubuntu 18.04 使用弹性网卡配置多个外网IP

    购买服务器后默认只有一个公网IP,经常会遇到单个外网IP无法满足业务需求,此文将介绍,一台服务器如何通过单网卡、多网卡配置多个IP。

    隔离没老王
  • RPA--机器人流程自动化工具介绍

    用户2017109
  • Qt音视频开发21-通用硬解码

    硬件解码是图形芯片厂家提出的用GPU资源解码视频流的方案,与之相对的是软解,也就是传统的用CPU承担解码工作的方案;优点是效率高,功耗低、热功耗低,缺点是缺乏有...

    feiyangqingyun
  • ReactiveCocoa函数响应式编程-基础篇目录:一、了解函数响应式编程二、ReactiveCocoa简介三、ReactiveCocoa集成四、ReactiveCocoa信号理解五、Reactiv

    一直以来,都很想学学ReactiveCocoa这个神奇的技术,但是最后都由于各种原因搁置了。这次终于也认真的研究一番,把自己学习心得整理出来留个记录。 目录: ...

    梧雨北辰
  • 18:肿瘤面积

    18:肿瘤面积 总时间限制: 1000ms 内存限制: 65536kB描述 在一个正方形的灰度图片上,肿瘤是一块矩形的区域,肿瘤的边缘所在的像素点在图片中用...

    attack
  • TensorFlow官方力推、GitHub爆款项目:用Attention模型自动生成图像字幕

    【新智元导读】近期,TensorFlow官方推文推荐了一款十分有趣的项目——用Attention模型生成图像字幕。而该项目在GitHub社区也收获了近十万“点赞...

    新智元

扫码关注云+社区

领取腾讯云代金券