android老司机教你“飙”EventBus3腾讯Bugly

EventBus对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码去实现多个模块之间的通信,而不需要以层层传递接口的形式去单独构建通信桥梁。从而降低因多重回调导致的模块间强耦合,同时避免产生大量内部类。它拥有使用方便,性能高,接入成本低和支持多线程的优点,实乃模块解耦、代码重构必备良药。

打开App的build.gradle,在dependencies中添加最新的EventBus依赖:

compile'org.greenrobot:eventbus:3.0.0'如果不需要索引加速的话,就可以直接跳到第二步了。而要应用最新的EventBusAnnotationProcessor则比较麻烦,因为注解解析依赖于android-apt-plugin。我们一步一步来,首先在项目gradle的dependencies中引入apt编译插件:

classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'然后在App的build.gradle中应用apt插件,并设置apt生成的索引的包名和类名:

applyplugin:'com.neenbedankt.android-apt'apt{arguments{eventBusIndex"com.study.sangerzhong.studyapp.MyEventBusIndex"}}接着在App的dependencies中引入EventBusAnnotationProcessor:

apt'org.greenrobot:eventbus-annotation-processor:3.0.1'这里需要注意,如果应用了EventBusAnnotationProcessor却没有设置arguments的话,编译时就会报错:NooptioneventBusIndexpassedtoannotationprocessor

此时需要我们先编译一次,生成索引类。编译成功之后,就会发现在\ProjectName\app\build\generated\source\apt\PakageName\下看到通过注解分析生成的索引类,这样我们便可以在初始化EventBus时应用我们生成的索引了。

EventBus默认有一个单例,可以通过getDefault()获取,也可以通过EventBus.builder()构造自定义的EventBus,比如要应用我们生成好的索引时:

EventBusmEventBus=EventBus.builder().addIndex(newMyEventBusIndex()).build();如果想把自定义的设置应用到EventBus默认的单例中,则可以用installDefaultEventBus()方法:

EventBus.builder().addIndex(newMyEventBusIndex()).installDefaultEventBus();1.3定义事件所有能被实例化为Object的实例都可以作为事件:

publicclassDriverEvent{publicStringinfo;}在最新版的eventbus3中如果用到了索引加速,事件类的修饰符必须为public,不然编译时会报错:Subscribermethodmustbepublic。

首先把作为订阅事件的模块通过EventBus注册监听:

mEventBus.register(this);在3.0之前,注册监听需要区分是否监听黏性(sticky)事件,监听EventBus事件的模块需要实现以onEvent开头的方法。如今改为在方法上添加注解的形式:

SubscriberclassXXXanditssuperclasseshavenopublicmethodswiththe@Subscribeannotation1.5发送事件调用post或者postSticky即可:

mEventBus.post(newDriverEvent("magnet:xt=urn:btih……"));到此我们就完成了使用EventBus的学习,可以在代码中尽情地飚车了。项目接入了EventBus之后会有什么好处呢?举一个常见的用例:ViewPager中Fragment的相互通信,就不需要在容器中定义各种接口,可以直接通过EventBus来实现相互回调,这样就把逻辑从ViewPager这个容器中剥离出来,使代码阅读起来更加直观。

在平时使用中我们不需要关心EventBus中对事件的分发机制,但要成为能够快速排查问题的老司机,我们还是得熟悉它的工作原理,下面我们就透过UML图来学习一下。

EventBus的核心工作机制透过作者Blog中的这张图就能很好地理解:

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

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

而下面的三个Poster,则是EventBus能在不同的线程执行回调方法的核心,我们根据不同的回调方式来看:

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

至此EventBus3的架构就分析完了,与之前EventBus老版本最明显的区别在于:分发事件的调度单位从订阅者,细化成了订阅者的回调方法。也就是说每个回调方法都有自己的优先级,执行线程和是否接收黏性事件,提高了事件分发的灵活程度,接下来我们在看核心功能的实现时更能体现这一点。

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

值得注意的是,老版本的EventBus是允许事件订阅者以不同的ThreadMode去监听同一个事件的,即在一个订阅者中有多个方法订阅一个事件,此时是无法保证这几个回调的先后顺序的,因为不同的线程回调是通过Handler调度的,有可能单个线程中的事件过多,事件受阻,回调则会比较慢。如今EventBus3使用了注解来表示回调后,还可以出现相同的ThreadMode的回调方法监听相同的事件,此时会根据注册的先后顺序,先注册先分发事件,注意不是先收到事件,收到事件的顺序还是得看poster中Handler的调度。

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

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

注销就比较简单了,把在注册时往两个数据结构中添加的订阅者信息删除即可:

上面经常会提到黏性事件,为什么要有这个设计呢?这里举个例子:我想在登陆成功后自动播放歌曲,而登陆和监听登陆监听是同时进行的。在这个前提下,如果登陆流程走得特别快,在登陆成功后播放模块才注册了监听。此时播放模块便会错过了【登陆成功】的事件,出现“虽然登陆成功了,回调却没执行”的情况。而如果【登陆成功】这个事件是一个黏性事件的话,那么即使我后来才注册了监听(并且回调方法设置为监听黏性事件),则回调就能在注册的那一刻被执行,这个问题就能被优雅地解决,而不需要额外去定义其他标志位。

在EventBus3的介绍中,作者提到以前的版本为了保证性能,在遍历寻找订阅者的回调方法时使用反射而不是注解。但现在却能在使用注解的前提下,大幅度提高性能,同时作者在博客中放出了这张对比图:

首先我们知道,索引是在初始化EventBus时通过EventBusBuilder.addIndex(SubscriberInfoIndexindex)方法传进来的,我们就先看看这个方法:

publicEventBusBuilderaddIndex(SubscriberInfoIndexindex){if(subscriberInfoIndexes==null){subscriberInfoIndexes=newArrayList<>();}subscriberInfoIndexes.add(index);returnthis;}可以看到,传进来的索引信息会保存在subscriberInfoIndexes这个List中,后续会通过EventBusBuilder传到相应EventBus的SubscriberMethodFinder实例中。我们先来分析SubscriberInfoIndex这个参数:

publicinterfaceSubscriberInfoIndex{SubscriberInfogetSubscriberInfo(Class<>subscriberClass);}可见索引只需要做一件事情——就是能拿到订阅者的信息。而实现这个接口的类如果我们没有编译过,是找不到的。这里就得看我们在一开始在配置gradle时导入的EventBusAnnotationProcessor:

索引的生成我们已经明白了,那么它是在哪里被应用的呢?我们记得在注册时,EventBus会通过SubscriberMethodFinder来遍历注册对象的Class的所有方法,筛选出符合规则的方法,并作为订阅者在接收到事件时执行的回调,我们直接来看看SubscriberMethodFinder.findSubscriberMethods()这个核心方法:

ListfindSubscriberMethods(Class<>subscriberClass){ListsubscriberMethods=METHOD_CACHE.get(subscriberClass);if(subscriberMethods!=null){returnsubscriberMethods;//先去方法缓存里找,找到直接返回}if(ignoreGeneratedIndex){//是否忽略设置的索引subscriberMethods=findUsingReflection(subscriberClass);}else{subscriberMethods=findUsingInfo(subscriberClass);}/**把找到的方法保存到METHOD_CACHE里并返回,找不到直接抛出异常*/}可以看到其中findUsingInfo()方法就是去索引中查找订阅者的回调方法,我们戳进去看看这个方法的实现:

-keepclassde.greenrobot.**{*;}其实我们仔细分析,可以看到是因为在SubscriberMethodFinder的findUsingReflection方法中,在调用Method.getAnnotation()时获取ThreadMode这个enum失败了,所以我们只需要keep住这个enum就可以了(如下)。

-keeppublicenumorg.greenrobot.eventbus.ThreadMode{publicstatic*;}这样就能正常编译通过了,但如果使用了索引加速,是不会有上面这个问题的。因为在找方法时,调用的不是findUsingReflection,而是findUsingInfo。但是使用了索引加速后,编译后却会报新的错误:CouldnotfindsubscribermethodinXXXClass.MaybeamissingProGuardrule

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

目前EventBus只支持跨线程,而不支持跨进程。如果一个app的service起到了另一个进程中,那么注册监听的模块则会收不到另一个进程的EventBus发出的事件。这里可以考虑利用IPC做映射表,并在两个进程中各维护一个EventBus,不过这样就要自己去维护register和unregister的关系,比较繁琐,而且这种情况下通常用广播会更加方便,大家可以思考一下有没有更优的解决方案。

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

本文总结了EventBus3的使用方法,运行原理和一些新特性,让大家能直观地看到这个组件的优点和缺点,同时让大家在考虑是否在项目中引入EventBus时心里有个底。最后感谢MarkusJunginger大神开源了如此实用的组件,以及组内同事在笔者探究EventBus原理时提供的帮助,希望大家在看完本文后都能有所收获,成为NB的Android开发老司机。

THE END
1.驾考宝典100题题库:小车 驾驶证: 小车 客车 货车 摩托车 资格证: 客运资格证 货运资格证 危险品资格证 教练员资格证 出租车资格证 驾校入驻驾校后台登录 首页 模拟考试 科目一科目二科目三科目四 找驾校 找驾校找教练找陪练 驾考圈 交通标志 软件下载 驾考宝典企业版 智慧驾校 买新车 买车网平行之家 二手车 https://www.jiakaobaodian.com/kaoshi/df06b6ae.html
2.开车那些事99条汽车驾驶技巧大全,很长,但请耐心看完吧学车技巧,开车技巧,为广大车友提供驾考宝典以及新手上路各种开车技巧。众车友交流驾驶技巧的第一站,生活中的用车指南! 一、开车技巧 行车安全的基础是技术和经验 1、上车先看车 上车前绕车转一圈,看车的外况、轮胎、车底下有没有漏油漏水。一个星期还得揭开盖子检查一次机油、冷却水、刹车油。 https://www.yoojia.com/ask/3-12111432967291349676.html
3.驾考刷题宝典大全10. 机动车驾驶人在参加接受交通安全教育扣减交通违法行为记分中弄虚作假的,由公安机关交通管理部门撤销相应的记分扣减记录,恢复相应记分,处多少元以下罚款?驾考刷题宝典大全告诉您通过驾校驾考一点通2024科目一的考试技巧,2024新版的驾考刷题宝典大全能助您快速通过驾考刷题宝典大全和驾校驾考一点通2024科目一模拟https://m.ybjk.com/ks/rcice.htm
4.驾考刷题宝典答案大全驾考刷题宝典答案大全是驾驶员考试网为大家搜集整理的,驾考刷题宝典答案大全可以帮助您快速通过模拟考试2025科目一科目四考试。 1驾驶载货汽车载物超过最大允许总质量未达到百分之三十的,一次记1分。正确错误 2持有大型客车、重型牵引挂车、城市公交车、中型客车、大型货车驾驶证的驾驶人,记分周期内有记分的,应当在记分https://mnks.jsyks.com/jcancb
5.免费驾考宝典大全下载驾考宝典下载官方版免费版驾考宝典免费的驾考宝典大全推荐给大家,能够在驾考的路上快人一步,软件中能够帮助你计算你的考试费用,一系列报考流程非常清楚,让你不被人坑;考试科目从科目一到科目四一应俱全,有着最新的考试题库辅助你,让你在考场上如鱼得水,专门针对驾驶执照考试的手机应用,让你无忧通过http://www.ishaohuang.com/s/mfdjkbdq/index_1.html
6.驾考宝典下载,科目一二三四模式考试软件,驾考宝典最新免费推荐理由:驾考宝典实时同步更新2024年驾考科目一、科目四考试题库,通过历年大数据深度分析,精选全国优质驾校供学员学车选择,用科学有效的方法助力学员高效备考驾照资格考试!完整学车流程,为学员提供从报名到领驾照的一站式驾考服务,同时提供了科目二科目三模拟练车新的实操学习方式。深受驾校、教练、司机、学员喜爱的驾驶员https://www.70wn.com/zt/jiakao/
7.用车宝典汽车知识日常用车小技巧汽车保养维修知识爱卡汽车网用车宝典频道为您提供最全最专业的驾车技巧,日常用车小技巧,日常用车注意事项,行车安全,用品配件,汽车维修保养知识,汽车文化等信息,更多汽车用车知识内容尽在爱卡汽车网。https://yp.xcar.com.cn/book/
8.驾车宝典行车上路开车注意事项指南大解析→MAIGOO知识摘要:随着生活条件的变好,汽车成为了人们日常出行的代步工具。很多考完驾照的朋友对于开车上路满心期待。相信对于安全驾驶的重要性,我们不必多说,因此养成良好的开车习惯对于安全驾驶来说十分的重要。下面和小编一起来看戏开车上路有哪些需要特别注意的地方。 https://m.maigoo.com/goomai/201896.html
9.驾考一点通官网科目一模拟考试2024最新版驾考一点通官网提供科目一模拟考试2024和驾驶员模拟考试试题2024,小车科目四模拟考试和2024驾校驾考一点通模拟考试c1,驾校驾考一点通2024科目一和驾校驾考一点通2024科目四分别使用驾考一点通2024科目一模拟考试和驾考一点通2024科目四模拟考试试题,驾考一点通助您驾驶员考https://m.jkydt.com/
10.驾照考试科目一模拟考试科目四模拟考试全国 小车 驾校 手机APP 驾校入驻 驾校后台 登录注册 热门驾校 友情链接 考题推荐 北京驾校天津驾校上海驾校重庆驾校杭州驾校宁波驾校南京驾校苏州驾校长春驾校石家庄驾校济南驾校青岛驾校福州驾校厦门驾校广州驾校深圳驾校南宁驾校海口驾校哈尔滨驾校乌鲁木齐驾校长沙驾校南昌驾校合肥驾校郑州驾校成都驾校昆明驾校贵阳驾校呼和浩特驾校https://jkbd.jxedt.com/
11.汽车驾驶图书排行榜京东JD.COM为您提供汽车驾驶图书销量排行榜、汽车驾驶图书哪个好、汽车驾驶图书多少钱等相关资讯,从汽车驾驶图书价格、评价、图片等多方面比较,为您推荐优质汽车驾驶图书产品!https://www.jd.com/phb/key_17135aea1be337afcb8d.html
12.驾驶员驾考学车考驾照必备宝典驾考通关更轻松。学车驾考题库练习宝典,科目一科目四驾驶员模拟考试一点通。采用2023年驾考新规题库,驾考知识点和交规训练,实时查看考试成绩并进行错题练习。http://www.jiakaoxintiku.com/
13.倒桩移库技巧图解由于夜间道路上的交通量小,行人和自行车的干扰也比较少,加上驾驶员的心理状态(如急于快赶等),一般比较容易高速行车,因而很可能发生交通事故。驾驶员应该充分认识到在夜间高速行车的危险性。夜间行车由亮处到暗处时,眼睛有一个适应过程,因此必须降低车速,在驶经弯道、坡路、桥梁、窄路和不易看清的地方更应降低车速http://www.360doc.com/content/12/0919/20/2006953_237090932.shtml
14.驾考宝典2022科目一考试科目四模拟考试驾照考试学车流程 驾考宝典整合学车全流程,驾考宝典为用户提供全方位学车服务 找驾校 口碑好的驾校 科目一 驾驶员理论考试 科目二 场地驾驶技能考试科目 科目三 机动车驾驶人道路考试 科目四 安全文明驾驶常识考试 拿本 开心拿本,安全驾驶 宝典荣誉 强大的驾考功能,优秀的用户体验,我们的愿景是学车无忧,轻松拿本教练宝典APP http://url.mucang.cn/1GNfo?channel=sina