EventBus 是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent, Handler, BroadCast 在 Fragment,Activity,Service,线程之间传递消息.优点是开销小,使用方便,可以很大程度上降低它们之间的耦合,使得我们的代码更加简洁,耦合性更低,提升我们的代码质量。

类似的库还有 Otto ,今天就带大家一起研读 EventBus 的源码.

在写这篇文章之前,我已经将本文相关的中文注释代码上传到了GitHub:https://github.com/kymjs/EventBus

基础用法

在读代码之前,首先你得了解它的基本用法.如果你已经能够很熟练的使用EventBus等事件总线库了,那么你可以跳过本节.

首先引入依赖包,查看GitHub主页的说明:https://github.com/greenrobot/EventBus

在Gradle文件加入 

compile 'de.greenrobot:eventbus:2.4.0'

用法与广播相同,且比广播更简单:

注册订阅者

首先你需要注册一个事件订阅者,为了方便理解你可以把他当成广播的广播接收者 你可以在任何一个类中使用如下代码注册以及解除注册

  1. //把当前类注册为订阅者(接收者)
  2. EventBus.getDefault().register(this);
  3. //解除注册当前类(同广播一样,一定要调用,否则会内存泄露)
  4. EventBus.getDefault().unregister(this);

注册了订阅者以后,我们需要创建一个回调方法onEvent,当我们订阅的事件发送的时候就会回调它

  1. //其实命名不一定必须是onEvent(),但那属于高级用法了,这里我们只说最简单的
  2. public void onEvent(Object event) {}

事件发送

当有了订阅者以后,我们的代码已经可以工作了.但是此时的代码是没有意义的,我们订阅的事件还没有发生. 就像广播需要一个sendBroadcast(),EventBus需要post(event) 

你可以在任何一个类中使用如下代码发送事件:

  1. /**
  2. * 这里的event类型必须和上面我们onEvent()方法的参数类型一致
  3. * (子父类关系也不行,必须是相同类型,原因我们下面看源码)
  4. */
  5. EventBus.getDefault().post(event);

至此,EventBus就可以正常工作了.如果希望了解原理请往下看。

进入源码世界

入口类EventBus类

我们从使用的流程来,首先看EventBus#getDefault()

  1. public static EventBus getDefault() {
  2. if (defaultInstance == null) {
  3. synchronized (EventBus.class) {
  4. if (defaultInstance == null) {
  5. defaultInstance = new EventBus();
  6. }
  7. }
  8. }
  9. return defaultInstance;
  10. }

只是简单的维护单例,调用构造方法,再看构造方法,调用重载的构造方法,重载的构造方法又需要一个EventBusBuilder对象

  1. public EventBus() {
  2. this(DEFAULT_BUILDER);
  3. }
  4. EventBus(EventBusBuilder builder) {
  5. }

EventBusBuilder类

看名字就知道,这个类是用来创建EventBus对象的.

Builder类提供了这么多个可选的配置属性,这里变量含义大家直接看我的注释,就不多作解释了

我们主要来看最终的建造方法

  1. /**
  2. * 根据参数创建对象,并赋值给EventBus.defaultInstance, 必须在默认的eventbus对象使用以前调用
  3. *
  4. * @throws EventBusException if there's already a default EventBus instance in place
  5. */
  6. public EventBus installDefaultEventBus() {
  7. synchronized (EventBus.class) {
  8. if (EventBus.defaultInstance != null) {
  9. throw new EventBusException("Default instance already exists." +
  10. " It may be only set once before it's used the first time to ensure " +
  11. "consistent behavior.");
  12. }
  13. EventBus.defaultInstance = build();
  14. return EventBus.defaultInstance;
  15. }
  16. }
  17. /**
  18. * 根据参数创建对象
  19. */
  20. public EventBus build() {
  21. return new EventBus(this);
  22. }

EventBusBuilder类提供了两种建造方法,还记得之前的getDefault()方法吗,维护了一个单例对象,installDefaultEventBus() 方法建造的EventBus对象最终会赋值给那个单例对象,但是有一个前提就是我们之前并没有创建过那个单例对象. 

这里大家思考一下,为什么如果EventBus.defaultInstance不为null以后程序要抛出异常?咱们之后说答案。

第二个方法就是默认的建造者方法了.

再回到我们的EventBus构造方法,根据提供的建造者初始化了一大堆属性

我们继续看这些初始化的字段.

三个Poster类

先是一大堆Map,看不懂,跳过去,我们先来看这三个Poster,需要说明的一点就是:Poster只负责处理粘滞事件,原因我们之后看代码。

  1. private final HandlerPoster mainThreadPoster; //前台发送者
  2. private final BackgroundPoster backgroundPoster; //后台发送者
  3. private final AsyncPoster asyncPoster; //后台发送者(只让队列第一个待订阅者去响应)

其实从类名我们就能看出个大概了,就是三个发送事件的方法。

我们来看看他们的内部实现. 

这几个Poster的设计可以说是整个EventBus的一个经典部分,越看越想继续多看几遍.

每个Poster中都有一个发送任务队列,PendingPostQueue queue;

进到队列里面再看 定义了两个节点,从字面上理解就是队列的头节点和尾节点

  1. private PendingPost head; //待发送对象队列头节点
  2. private PendingPost tail;//待发送对象队列尾节点

再看这个PendingPost类的实现:

  1. //单例池,复用对象
  2. private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
  3. Object event; //事件类型
  4. Subscription subscription; //订阅者
  5. PendingPost next; //队列下一个待发送对象

首先是提供了一个的设计,类似于我们的线程池,目的是为了减少对象创建的开销,当一个对象不用了,我们可以留着它,下次再需要的时候返回这个保留的而不是再去创建。

再看最后的变量,PendingPost next 非常典型的队列设计,队列中每个节点都有一个指向下一个节点的指针(sorry,数据结构用C学的)。

  1. /**
  2. * 首先检查复用池中是否有可用,如果有则返回复用,否则返回一个新的
  3. *
  4. * @param subscription 订阅者
  5. * @param event 订阅事件
  6. * @return 待发送对象
  7. */
  8. static PendingPost obtainPendingPost(Subscription subscription, Object event) {
  9. synchronized (pendingPostPool) {
  10. int size = pendingPostPool.size();
  11. if (size > 0) {
  12. PendingPost pendingPost = pendingPostPool.remove(size - 1);
  13. pendingPost.event = event;
  14. pendingPost.subscription = subscription;
  15. pendingPost.next = null;
  16. return pendingPost;
  17. }
  18. }
  19. return new PendingPost(event, subscription);
  20. }
  1. /**
  2. * 回收一个待发送对象,并加入复用池
  3. *
  4. * @param pendingPost 待回收的待发送对象
  5. */
  6. static void releasePendingPost(PendingPost pendingPost) {
  7. pendingPost.event = null;
  8. pendingPost.subscription = null;
  9. pendingPost.next = null;
  10. synchronized (pendingPostPool) {
  11. // 防止池无限增长
  12. if (pendingPostPool.size() < 10000) {
  13. pendingPostPool.add(pendingPost);
  14. }
  15. }
  16. }

obtainPendingPost(),对池复用的实现,每次新创建的节点尾指针都为 null 。

releasePendingPost(),回收pendingPost对象,既然有从池中取,当然需要有存。这里,原作非常细心的加了一次判断,if
(pendingPostPool.size() < 10000)
 其实我觉得10000都很大了,1000就够了,我们一次只可能创建一个pendingPost,如果ArrayList里面存了上千条都没有取走,那么肯定是使用出错了。

PendingPost的代码我们就看完了,再回到上一级,队列的设计:

接着是PendingPostQueue的入队方法

  1. synchronized void enqueue(PendingPost pendingPost) {
  2. ...
  3. if (tail != null) {
  4. tail.next = pendingPost;
  5. tail = pendingPost;
  6. } else if (head == null) {
  7. head = tail = pendingPost;
  8. }
  9. ...
  10. }

首先将当前节点的上一个节点(入队前整个队列的最后一个节点)的尾指针指向当期正在入队的节点(传入的参数pendingPost),并将队列的尾指针指向自己(自己变成队列的最后一个节点),这样就完成了入队。

如果是队列的第一个元素(队列之前是空的),那么直接将队列的头尾两个指针都指向自身就行了。

出队也是类似的队列指针操作

  1. synchronized PendingPost poll() {
  2. PendingPost pendingPost = head;
  3. if (head != null) {
  4. head = head.next;
  5. if (head == null) {
  6. tail = null;
  7. }
  8. }
  9. return pendingPost;
  10. }

首先将出队前的头节点保留一个临时变量(它就是要出队的节点),拿到这个将要出队的临时变量的下一个节点指针,将出队前的第二个元素(出队后的第一个元素)的赋值为现在队列的头节点,出队完成。 

值得提一点的就是,PendingPostQueue的所有方法都声明了synchronized,这意味着在多线程下它依旧可以正常工作,细想想这也是必须的,对吗?

再回到上一级,接着是HandlerPoster的入队方法enqueue(),

  1. /**
  2. * @param subscription 订阅者
  3. * @param event 订阅事件
  4. */
  5. void enqueue(Subscription subscription, Object event) {
  6. PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
  7. synchronized (this) {
  8. queue.enqueue(pendingPost);
  9. if (!handlerActive) {
  10. handlerActive = true;
  11. if (!sendMessage(obtainMessage())) {
  12. throw new EventBusException("Could not send handler message");
  13. }
  14. }
  15. }
  16. }

入队方法会根据参数创建 待发送对象 pendingPost 并加入队列,如果此时 handleMessage() 没有在运行中,则发送一条空消息让 handleMessage 响应 

接着是handleMessage()方法

  1. @Override
  2. public void handleMessage(Message msg) {
  3. boolean rescheduled = false;
  4. try {
  5. long started = SystemClock.uptimeMillis();
  6. while (true) {
  7. PendingPost pendingPost = queue.poll();
  8. if (pendingPost == null) {
  9. synchronized (this) {
  10. // 双重校验,类似单例中的实现
  11. pendingPost = queue.poll();
  12. if (pendingPost == null) {
  13. handlerActive = false;
  14. return;
  15. }
  16. }
  17. }
  18. //如果订阅者没有取消注册,则分发消息
  19. eventBus.invokeSubscriber(pendingPost);
  20. //如果在一定时间内仍然没有发完队列中所有的待发送者,则退出
  21. long timeInMethod = SystemClock.uptimeMillis() - started;
  22. if (timeInMethod >= maxMillisInsideHandleMessage) {
  23. if (!sendMessage(obtainMessage())) {
  24. throw new EventBusException("Could not send handler message");
  25. }
  26. rescheduled = true;
  27. return;
  28. }
  29. }
  30. } finally {
  31. handlerActive = rescheduled;
  32. }
  33. }

handleMessage()不停的在待发送队列queue中去取消息。 需要说明的是在循环之外有个临时boolean变量rescheduled,最后是通过这个值去修改了handlerActive。而
handlerActive 是用来判断当前queue中是否有正在发送对象的任务,看到上面的入队方法enqueue(),如果已经有任务在跑着了,就不需要再去sendMessage()唤起我们的handleMessage()

最终通过eventBus对象的invokeSubscriber()最终发送出去,并回收这个pendingPost,让注册了的订阅者去响应(相当于回调),至于这个发送方法,我们之后再看。

看完了HandlePoster类,另外两个异步的发送者实现代码也差不多,唯一的区别就是另外两个是工作在异步,实现的Runnable接口,大家自己类比,这里就不帖代码了.

Poster工作原理

最后我们再来回顾一下PosterPendingPostQueuePendingPost这三个类,再看看下面这张图,是不是有种似曾相识的感觉。

啊哈,那是HandleMessageLooper的工作原理,再看看Poster的

至此,整个EventBus源码的发送接收核心部分已经分析完了。

还记得上面我们留下的那几个问题吗:

1、为什么如果EventBus.defaultInstance不为null以后程序要抛出异常?

2、Poster只对粘滞事件有效的说明代码在哪。

3、invokeSubscriber()最终的发送怎么实现的。

接下来我们继续分析它的注册流程以及粘滞事件的设计(那又是一个经典的地方)。

android EventBus详解(一)的更多相关文章

  1. android EventBus详解(三)

    post()方法调用流程 我们继续来看EventBus类,的另一个入口方法post() //已省略部分代码 public void post(Object event) { PostingThread ...

  2. android EventBus详解(二)

    上一节讲了EventBus的使用方法和实现的原理,下面说一下EventBus的Poster只对粘滞事件和invokeSubscriber()方法是怎么发送的. Subscribe流程 我们继续来看Ev ...

  3. EventBus详解

    EventBus详解 简介 github原文 EventBus... * simplifies the communication between components - decouples eve ...

  4. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  5. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  6. Android ActionBar详解

    Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar   目录(?)[+]   第4 ...

  7. Android 签名详解

    Android 签名详解 AndroidOPhoneAnt设计模式Eclipse  在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...

  8. Android编译系统详解(一)

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...

  9. Android布局详解之一:FrameLayout

      原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...

随机推荐

  1. UNIX网络编程——原始套接字的魔力【续】

    如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...

  2. Android初级教程:如何自定义一个状态选择器

    有这样一种场景:点击一下某个按钮或者图片(view),改变了样式(一般改变背景颜色).这个时候一种解决方案,可能就是状态选择器.接下来就介绍如何实现状态选择器: 步骤: 一.新建这样的文件夹:res/ ...

  3. 【转载】图灵AngularJS入门教程

    摘自图灵的AngularJS入门教程:http://www.ituring.com.cn/article/13471 感觉非常不错,所以推荐到首页一下! (一)Hello World! 开始学习Ang ...

  4. 错误:One or more post-processing actions failed. Consult the OPP service log for details

     今天在做采购出入库明细报表的时候,有的时候能正常打印,有的时候报 One or more post-processing actions failed. Consult the OPP serv ...

  5. JqGrid 显示表格

    JqGrid 是前台的表格显示库,使用起来相当方便. 这里分享下本人使用过程中遇到的问题及解决方案 ** 一.rowNum属性 ** 1.如果不设置,默认显示数是20,也就是说超过20以后的数据.不再 ...

  6. (八十七)AutoLayout的简介与实例

    AutoLayout是继AutoResizing之后的一种自动布局方法,解决了AutoResizing无法处理控件间相互关系的问题. AutoLayout在storyboard中通过底部工具条设置,底 ...

  7. SHA算法

    安全Hash函数(SHA)是使用最广泛的Hash函数.由于其他曾被广泛使用的Hash函数都被发现存在安全隐患,从2005年至今,SHA或许是仅存的Hash算法标准. SHA发展史 SHA由美国标准与技 ...

  8. ElGamal密码

    ElGamal也是一种基于离散对数的公钥体制,与Diffie-Hellman密钥体制密切相关.ElGamal密码体系用于数字签名标准(DSS)和S/MIME电子邮件标准等一些技术标准中. 算法描述: ...

  9. python 内存数据库与远程服务

    python 内存数据库与远程服务 需要import的python 内存数据库代码参考下面的链接: http://blog.csdn.net/ubuntu64fan/article/details/5 ...

  10. 判断无向图是否有环路的方法 -并查集 -BFS

    可以利用并查集或者带颜色标记的BFS(来自算法导论)判断. 首先介绍第一种,用并查集来判断: 首先初始化所有元素的根为-1,-1代表根节点,接下来对于图中的每一条边(v1,v2)都并入集合,并入的方式 ...