热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

EventBus3.0

作者:MarkusJungingerGithub:greenrobotEventBus原文:老司机教你“飙”EventBus3Iswhat基于观察者模式的事件发布订阅框架。通过极少

作者:Markus Junginger
Github:greenrobot/EventBus
原文:老司机教你 “飙” EventBus 3

Is what

基于观察者模式的事件发布/订阅框架。
通过极少的代码实现模块间的通信,无须层层传递。使用方便,性能高,接入成本低,降低耦合,支持多线程。

《EventBus 3.0》 流程图

3.0 新特性

EventBus 3.0版本中引入了 EventBusAnnotationProcessor(注解分析生成索引)技术,大大提高了EventBus的运行效率。

1. 使用

《EventBus 3.0》 流程图

1.1 添加依赖

build.gradle 中添加依赖:

compile'org.greenrobot:eventbus:3.0.0'

1.2 添加加速索引
  • 在项目gradledependencies中引入apt编译插件:

classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'

  • Appbuild.gradle中应用apt插件,并设置apt生成的索引的包名和类名:

applyplugin:'com.neenbedankt.android-apt'
apt {
arguments{
eventBusIndex"com.study.sangerzhong.studyapp.MyEventBusIndex"
}
}

  • Appdependencies中引入EventBusAnnotationProcessor

apt'org.greenrobot:eventbus-annotation-processor:3.0.1'

注意:

  1. 注解解析依赖于android-apt-plugin
  2. 加速索引可以不加;
  3. 应用EventBusAnnotationProcessor却没有设置arguments,编译时会报错:No option eventBusIndex passed to annotation processor,此时需要先编译一次,生成索引类。编译成功之后,会在\ProjectName\app\build\generated\source\apt\PakageName\下看到通过注解分析生成的索引类,如此便可在初始化EventBus时应用生成的索引了。
1.3 初始化

两种初始方式:

  1. 默认单例获取EventBus默认有一个实例)

EventBus mEventBus = EventBus.getDefault();

  1. 自定义

//如:应用生成好的索引时
EventBus mEventBus = EventBus.builder()
.addIndex(new MyEventBusIndex())
.build();
//如:自定义的设置应用到默认单例中
EventBus mEventBus = EventBus.builder()
.addIndex(newMyEventBusIndex())
.installDefaultEventBus();

1.4 定义事件

所有能被实例化为 Object 的实例都可以作为事件:

public class DriverEvent {
public String info;
}

注意:若使用了索引加速,事件类的修饰符必须为public,否则编译时会报错:Subscriber method must be public

1.5 监听事件

在订阅事件(接收事件)的模块,注册EventBus

//如:Activity 中可写在 onCreate() 方法内
mEventBus.register(Object);

在订阅事件(接收事件)的模块,注销EventBus

//如:Activity 中可写在 onDestory() 方法内
mEventBus.unregister(Object);

3.0前,需要区分是否监听黏性(sticky)事件。
3.0中,改为添加注解的形式:

@Subscribe(threadMode = ThreadMode.POSTING, priority =0, sticky =true)
public void handleEvent ( DriverEventevent ){
Log.d(TAG,event.info);
}

注解有三个参数:

  • threadMode: 回调所在的线程
  • priority: 优先级
  • sticky: 是否接收黏性事件

注册监听模块必须有一个标注Subscribe注解方法,否则register时会抛出异常:
Subscriberclass XXX and its super classes havenopublic methods with the@Subscribeannotation

1.6 发送事件

调用postpostSticky即可。

接收事件的模块需要注册
发送事件的模块无须注册

EventBus.getDefault().post(new DriverEvent("magnet:?xt=urn:btih……"));

以上便完成了EventBus的学习。

总结

  • 实际使用中,registerunregister 通常与 ActivityFragment 的生命周期相关;
  • ThreadMode.MainThread解决了界面刷新必须在UI线程的问题;
  • 黏性事件可以解决了 postregister同时执行时的异步问题;
  • 事件传递没有序列化与反序列化的性能消耗。

2. 原理分析

2.1 和新架构

《EventBus 3.0》 机制

订阅者模块需要通过EventBus订阅相关的事件,并准备好处理事件的回调方法;
事件发布者则在适当的时机把事件post出去,EventBus就能搞定一切。

在架构方面,EventBus 3.0与之前稍老版本有不同,如图:

《EventBus 3.0》 架构

核心类EventBus,其中subscriptionByEventType是以事件的类为key,订阅者的回调方法为value的映射关系表。即EventBus在收到事件时,可根据该事件的类型,在subscriptionByEventType中找到所有监听了该事件的订阅者及处理事件的回调方法。typesBySubscriber是每个订阅者所监听的事件类型表,在取消注册时通过该表中保存的信息,快速删除subscriptionByEventType中订阅者的注册信息,避免遍历查找。注册事件、发送事件和注销都是围绕着这两个核心数据结构来展开。Subscription可以理解为每个订阅者与回调方法的关系,在其他模块发送事件时,会通过这个关系,让订阅者执行回调方法。

回调方法在这里被封装成了SubscriptionMethod,里面保存了在需要反射invoke方法时的各种参数,包括优先级,是否接收黏性事件和所在线程等信息。而要生成这些封装好的方法,则需要SubscriberMethodFinder,它可以在regster时得到订阅者的所有回调方法,并封装返回给EventBus。右边的加速器模块,是为了提高SubscriberMethodFinder的效率。

四种Poster:是EventBus能在不同的线程执行回调方法的核心

  • POSTING:调用post所在的线程执行回调,不需要poster调度,直接运行;
  • MAIN:UI线程回调,如果postUI线程则直接执行,否则通过mainThreadPoster调度;
  • BACKGROUND:Backgroud线程回调,如果post不在UI线程则直接执行,否则通过backgroundPoster调度;
  • ASYNC:交给线程池管理,直接通过asyncPoster调度。

不同的Poster会在post事件时,调度相应的事件队列PendingPostQueue,让每个订阅者的回调方法收到相应的事件,并在其注册的Thread中运行。而这个事件队列是一个链表,由一个个PendingPost组成,其中包含了事件,事件订阅者,回调方法这三个核心参数,以及需要执行的下一个PendingPost

2.2 register

根据订阅者的类来找回调方法,把订阅者和回调方法封装成关系,并保存到相应的数据结构中,为随后的事件分发做好准备,最后处理黏性事件:

《EventBus 3.0》 register

EventBus 3.0使用了注解表示回调,可以出现相同的ThreadMode的回调方法监听相同的事件,此时会根据注册的先后顺序,先注册先分发事件,注意不是先收到事件,收到事件的顺序还是得看posterHandler的调度。

2.3 post

分析事件后,得到所有监听该事件的订阅者的回调方法,并利用反射来invoke方法,实现回调:

《EventBus 3.0》 post

图中看到poster的调度事件功能,同时调度的单位细化成了Subscription,即每一个方法都有自己的优先级和是否接收黏性事件。源代码中为了保证post执行不会出现死锁,等待和对同一订阅者发送相同的事件,增加了很多线程保护锁和标志位。

2.4 unregister

把在注册时往两个数据结构中添加的订阅者信息删除即可:

《EventBus 3.0》 unregister

2.5 黏性事件

举栗:在登陆成功后自动播放歌曲,登陆和监听登陆是同时进行的。

  • 正常情况:如果登陆流程走得快,在登陆成功后播放模块才注册了监听。此时播放模块便错过了【登陆成功】的事件,出现“虽然登陆成功了,回调却没执行”的情况。
  • 粘性事件:如果【登陆成功】是黏性事件,即使后来才注册了监听(回调方法设置为监听黏性事件),则回调就能在注册的那一刻被执行,无需额外定义其他标志位。

3 索引加速

旧版本为了保证性能,在遍历寻找订阅者的回调方法时使用反射而不是注解。而新版本在使用注解的前提下,大幅度提高了性能。作者放出的对比图:

《EventBus 3.0》 速度对比

性能方面,EventBus 3.0由于使用了注解,比起使用反射来遍历方法的2.4版本逊色不少。但开启索引后性能远远超出旧版本。

关于索引加速的具体分析请看原文。

4 其他

4.1 混淆

EventBus 3.0使用注解的方式。作者的意思是在混淆时就不用再keep住相应的类和方法。

//运行时,抛出错误
java.lang.NoSuchFieldError: No static field POSTING。
//解决方法:keep住所有eventbus相关的代码
-keepclassde.greenrobot.** {*;}

分析,在SubscriberMethodFinder的findUsingReflection方法中,在调用Method.getAnnotation()时获取ThreadMode这个enum失败了,所以需要keep住这个enum(如下)。

-keeppublicenumorg.greenrobot.eventbus.ThreadMode {
publicstatic*;
}

这样就能编译通过了,如果使用了索引加速,是不会有上面这个问题的。因为在找方法时,调用的不是findUsingReflection,而是findUsingInfo

//使用索引加速后,编译后抛出错误:
Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

因为生成索引GeneratedSubscriberIndex是在代码混淆之前进行的,混淆之后类名和方法名都不一样了(上面这个错误是方法无法找到),需要keep住所有被Subscribe注解标注的方法:

-keepclassmembersclass* {
@de.greenrobot.event.Subscribe ;
}

这里就得权衡一下利弊:使用了注解不用索引加速,则只需要keepEventBus相关的代码,现有的代码可以正常的进行混淆。而使用了索引加速的话,则需要keep住相关的方法和类。

4.2 跨进程

目前EventBus支持跨线程,不支持跨进程。这里可以考虑利用IPC做映射表,并在两个进程中各维护一个EventBus,不过这样就要自己去维护registerunregister的关系,比较繁琐,这种情况下用广播更加方便。

4.3 事件环路

使用EventBus,通常会把两个模块相互监听,来达到相互回调通信的目的。一旦出现死循环,且没有相应的日志信息,很难定位问题。所以如果在回调上有环路,且回调方法十分复杂,就要考虑把接收事件专门封装成一个子模块,同时考虑避免出现事件环路。

5 写在最后

EventBus并不是重构代码的唯一之选。作为观察者模式的“同门师兄弟”——RxJava,作为功能更为强大的响应式编程框架,可以轻松实现EventBus的事件总线功能(RxBus)。但毕竟大型项目要接入RxJava的成本高,复杂的操作符需要开发者投入更多的时间去学习。所以在成熟的项目中快速地重构、解耦模块,EventBus是不二之选。

《EventBus 3.0》 老司机发车了


推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
author-avatar
刘美娥94662
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有