EventBus初理解

 缘由:

平时工作,因为懒于动笔的原因,也没注重技术和经验的积累,导致之前曾经研究过的问题现在又忘记了,所以要慢慢注重积累,那么就从写作开始,谈谈对工作中碰到的问题进行整理和归纳。

我们都知道,在Android中,想处理事件传递,可以用Handler+MessageQueue+Message+Looper循环,固然是有解决方法,但是这个使用起来不方便,代码写起来也不简洁,同时还必须要理解好Handler+MessageQueue+Message+Looper之间的关系,比如这样的图:

是不是看到觉得头大,理解起来也麻烦,但是这个原理是很有必要去了解的,这个也是基本功之一吧。今天我们来讲讲对于EventBus这个开源库的分析,看看高手代码是如何写的,如何解耦合,对于自身的技术的进步相比也是很有大的帮助吧。

 初认识:

    因为在工作大量用到Fragment和Activity之间的耦合,需要获取对方某某的实例,自然而然就去寻找事件之间是否有解耦合的库,EventBus就涌现出来了,这个是greenrobot大神写的,多么经典已经无需多说了,github地址:https://github.com/greenrobot/EventBus,虽然我们会使用,但是也需要了解里面的原理,这样使用的起来也放心,毕竟对于陌生的东西不明白内部情况,就去用,什么时候被坑也会不知道的。

    脑补下:

介绍这个库之前,需要认识以下几个名词。

事件(Event):又可称为消息,本文中统一用事件表示。其实就是一个对象,可以是网络请求返回的字符串,也可以是某个开关状态等等。事件类型(EventType)指事件所属的 Class。事件分为一般事件和 Sticky 事件,相对于一般事件,Sticky 事件不同之处在于,当事件发布后,再有订阅者开始订阅该类型事件,依然能收到该类型事件最近一个 Sticky 事件。

订阅者(Subscriber):订阅某种事件类型的对象。当有发布者发布这类事件后,EventBus 会执行订阅者的 onEvent 函数,这个函数叫事件响应函数。订阅者通过 register 接口订阅某个事件类型,unregister 接口退订。订阅者存在优先级,优先级高的订阅者可以取消事件继续向优先级低的订阅者分发,默认所有订阅者优先级都为 0。

发布者(Publisher):发布某事件的对象,通过 post 接口发布事件。

  结构图:

从中,我们可以看到这个库整体结构是基于生产者/消费者模式,也可以称呼为发布者/订阅者,显得更亲切。所谓的发布者/订阅者,比如我们日常生活中,小区的通告栏,生产者类似小区居委会,订阅者就是小区里的住户,当你需要小区居委会帮忙的时候,比如你外出了,可以叫居委会里某个大妈帮你拿下快递,那么你需要在居委会那里登记,也可以叫做注册,只有登记了,居委会里的大妈才知道你是我们同一个小区的,也可以认识你了。同时,每当有事情发生,需要通知的时候,需要在通告栏里发布一个通告,现在信息发达了,通告栏可以通过微信公众号来发布,但是也需要每个人去扫一扫,关注下小区的公众号,一发通知,有关注的业主自然就知道通知事情是什么。那么这个EventBus库的实现原理,跟我们日常生活中的微信公众号很类型,首先在使用的时候,我们需要向公众平台注册,注册完之后, 当生产者需要发布信息的时候,平台会帮我们把这些消息推送给订阅者,订阅者根据消息内容,进行不同处理操作。

使用方法流程:

    类图:

    

    根据类来分析,从中,我们可以看到EventBus依赖有五个类,分别如下。

SubscriberMethod:这是一个订阅方法类的封装,包含了方法反射的名称,线程模型和事件类型,比如当你向主干注册的时候,这时候就会实例化SubscriberMethod类实例,来存放以onEvent开头的方法,运行的线程模型和自定义的传递事件类型。

SubscriberMethodFinder:在这个类中,我们可以看到为何在定义方法消息接受回调时,会以“onEvent”开头的方法,因为这里有个

private static final String ON_EVENT_METHOD_NAME = "onEvent";

ON_EVENT_METHOD_NAME的常量。里面关键方法是findSubscriberMethods(),具体实现可以去看代码,通过反射的方法,找出订阅者上的以onEvent开头的方法,最终返回的时候SubscriberMethod类的集合,也就是所有事件响应函数。

     HandlerPoster:这是继承Handler的请求类,封装了请求队列,整个过程是在队列不断发送请求,直到所有的请求都出队列,也就是全部发送完毕。事件主线程处理,对应ThreadMode.MainThread。enqueue 函数将事件放到队列中,并利用 handler 发送 message,handleMessage 函数从队列中取事件,invoke 事件响应函数处理。

     AsyncPoster:这个其实一个Runnable实现类,封装执行的过程,在异步时调用。是一个事件异步线程处理,对应ThreadMode.Async。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。

     BackgroundPoster:这个其实一个Runnable实现类,事件 Background 处理,对应ThreadMode.BackgroundThread。enqueue 函数将事件放到队列中,并调用线程池执行当前任务,在 run 函数从队列中取事件,invoke 事件响应函数处理。与 AsyncPoster.java 不同的是BackgroundPoster 中的任务只在同一个线程中依次执行,而不是并发执行。

    ThreadMode模型

线程模型共有四类:

  • PostThread,默认的ThreadMode,表示在执行Post操作的线程直接调用订阅者的事件响应方法不论该线程是否为主线程(UI线程)。当该线程为主线程时,响应方法不能有耗时操作,否则有卡主线程风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作。
  • MainThread,在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
  • BackgroundThread,在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
  • Async,不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。

   EventBus类介绍

EventBus类是对外暴露的API,包括register(),post(),unregister()方法,配合自定义的EventTypeji s事件响应,即可完成使用过程。

EventBus类有个默认getDefault单例,当然也可以通过EventBusBuilder 或构造函数新建一个EventBus,每个新建的EventBus发布和订阅事件都是相互隔离的,即在一个EventBus对象中发布事件,如果有另外一个EventBus对象,则另外一个EventBus对象的订阅者不会收到该订阅。

关键点:

1,register和unregister,分别表示订阅事件和取消订阅,register 最底层函数有三个参数,分别为订阅者对象、是否是 Sticky 事件、优先级。

注册流程:

register 函数中会先根据订阅者类名去subscriberMethodFinder中查找当前订阅者所有事件响应函数,然后循环每一个事件响应函数,依次执行下面的 subscribe 函数:

第一,通过subscriptionsByEventType得到该事件类型的所有订阅者信息队列,根据优先级把当前订阅者信息插入到订阅者队列subscriptionsByEventType中。

第二,在typesBySubscriber中得到当前订阅者的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅。

第三,检查这个事件是否是 Sticky 事件,如果是则从stickyEvents事件保存队列中取出该事件类型最后一个事件发送给当前订阅者。

发布流程:

post方法,用于发布事件,cancle方法用户取消订阅者订阅的所有事件类型。

post 方法会首先得到当前线程的 post 信息PostingThreadState,其中包含事件队列,将当前事件添加到其事件队列中,然后循环调用 postSingleEvent 函数发布队列中的每个事件。

postSingleEvent 方法会先去eventTypesCache得到该事件对应类型的的父类及接口类型,没有缓存则查找并插入缓存。循环得到的每个类型和接口,调用 postSingleEventForEventType 方法发布每个事件到每个订阅者。

postSingleEventForEventType 方法在subscriptionsByEventType查找该事件订阅者订阅者队列,调用 postToSubscription 函数向每个订阅者发布事件。

结尾:

重点名称解释:

  • typesBySubscriber订阅者订阅的事件的保存队列,以 subscriber 为 key,元素为 eventType 的 ArrayList 为 Value。
  • currentPostingThreadState当前线程的 post 信息,包括事件队列、是否正在分发中、是否在主线程、订阅者信息、事件实例、是否取消。
  • mainThreadPoster、backgroundPoster、asyncPoster事件主线程处理者、事件 Background 处理者、事件异步处理者。
  • subscriberMethodFinder订阅者响应函数信息存储和查找类。
  • executorService异步和 BackGround 处理方式的线程池。

参考地址:http://www.codekk.com/open-source-project-analysis/detail/Android/Trinea/EventBus%20源码解析

时间: 2024-08-07 21:21:01

EventBus初理解的相关文章

JavaScript——面向对象初理解

JS事件驱动,基于对象的非编译性语言,所以首先来看看对象的创建问题. JS中对象的创建不像之前学过的语言使用class关键字,分为两大类,一般的创建方式和使用function关键字的.一般的方式每次相当于创建了了一个对象,如果想要创建类似的对象就会有大量的相似代码,这不符合面向对象,所以使用function相当于是使用class创建以一个类,可以通过类来实例化对象. 一般的创建对象的实例 //使用new Object()方法 var box=new Object();//创建一个对象,new可以

ASCII初理解

ASCII:美国标准信息交换码,是现今最通用的单字节编码系统.这里先简单的理解一下编码是什么意思.编码是信息从一种形式或格式幻化为另一种形式的过程.在这个过程中需要有一种转换约定.计算机中所有的数据再存储和运行的时候都是所有的信息都使用二进制数表示的(0,1),而具体是哪些二进制数据表示哪些符号,每个人都可以约定自己的一套,如果别人想读懂自己的数据,就需要知道自己的编码规则.而ASCII编码,就是一个标准化的编码规格,同一的规定了常用的符号要用哪些二进制来表示.就相当于,普通话和方言的关系.同一

Maximum Entropy Model(最大熵模型)初理解

1,简单概率知识理解 1.1 随机变量(random variable) 表示随机现象(在一定条件下,并不总是出现相同结果的现象称为随机现象)各种结果的实值函数(一切可能的样本点).如掷一颗骰子,它的所有可能结果是出现1点.2点.3点.4点.5点和6点 ,若定义X为掷一颗骰子时出现的点数,则X为一随机变量.随机变量   X∈{1,2,3,4,5,6}.

prototype和constructor之初理解

因为整理以前的笔记,发现了这两个东西,也算是我对他们最初的最幼稚的理解吧. 乍一回忆,prototype是为一个大的类增加行为用的,而这个行为实例们普遍都要用到:而constructor顾名思义是构造,构造函数? 先说说prototype,因为理解的很浅,感觉没啥好说的,直接上个例子: 1 function Person() { 2 this.age = 20; 3 this.name = 'hello world'; 4 } 5 6 Person.prototype.show = functi

设计初理解

设计,是"需求上升到机制"的过程和结果.不是针对一个需求,而是设计一种机制,容纳和集成一类相似需求.基于需求,超越需求. 设计需要在安静的环境里,反复剖析事物的内在结构和规律,洞悉事物之间的关联,然后用精炼的方式表达出来.设计是一门科学,为学必当严谨. 在电脑旁,人一般是执行者角色.耳之所听,目之所视,都是一种干扰.只有在远离电脑的时候,在冬日暖阳的微风拂面中,内心和头脑得到暂时的放松,人才会转变成设计者角色.设计是谋定而后动的事情. 事物内在结构:平铺.嵌套.镜像.迭代.弯曲.组合.

初理解Java中的BIO,NIO,AIO

初识: java 中的 BIO.NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装.程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码.只需要使用Java的API就可以了. 在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞. 同步与异步: 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回. 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但

JavaScript——初理解闭包及作用

js是一个函数级语言,变量的作用域是: 内部可以访问内部,内部可以访问外部,外部不能访问内部. 如果要在外部,访问函数内部的变量,就要用到闭包.闭包就是指访问到了本不该访问的变量. 闭包作用1:实现封装 先来看一个关于封装的例子,在person之外的地方无法访问其内部的变量,而通过提供闭包的形式来访问: 1 var person = function(){ 2 //变量作用域为函数内部,外部无法访问 3 var name = "default"; 4 5 return { 6 getN

小白初理解树状数组

ACM的在线测试里经常涉及到大量数据的的修改,求和等操作,这里介绍一种方法——树状数组. 树状数组,是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值:经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值.可以用一张图来弄懂什么是数组数组. 原数组A[n],树状数组C[n]; 如果n为奇数:Cn=An; 如果n为偶数:Cn = A(n – 2^k + 1) + ... + An,k为n的二进制数

ajax初理解

uri:统一资源标示符 url:统一资源定位符,包括三个部分:方案,地址,资源 urn:统一资源名称 一个http事务是由一个请求和一个相应构成,不管在任何情况下,这种通信通过名为http宝文的格式化数据进行的 ajax是基于http的 get方法是向服务器获取东西 post是向服务器输送东西 respons是主题,存放数据的,里面的内容可以是动态的也可以是静态啊 127.0.0.1:8081====localhost:8081 axaj四步: 1创建请求对象 2打开这个对象 3注册当ready