|
|
|
|
移动端
创建专栏

“入职接手旧项目,所有网络请求数据通过 EventBus 分发,吓得我想离职...”

本文来源:http://www.2233122.com/zixun_lingtuan_com/

太阳城娱乐网最快登入,  据一位专家计算,在严重污染条件下,PM2.5浓度为200微克每立方米,该塔每小时吸收PM2.5总量为4.5克。”对于该打印机的具体型号,该工作人员表示在开车不方便说,有时间会好好解释。某日,一颗美国的间谍卫星在对苏联里海军事基地的例行侦查中,突然有了大发现。“辽宁号”和两艘在建航母使用的都是没有弹射器的起飞系统,从而限制了飞机的起飞携载能力。

他表示,以前进了3吨煤气,几天就送完了;这次同样进了3吨,好几天下去了,销量却明显少了。接到报案后,中国驻意使馆第一时间启动领事保护应急机制,一直在密切关注案情发展。要改进监管方式手段,更多采用市场化、法治化、信息化监管方式。中国政府一贯坚决反对与中国建交的国家与台湾地区开展任何形式的官方往来,包括军事交流与合作。

成本能降低大概20%土地的流转使得以往小块的农田变成了大田,随着大量田埂恢复成为农田,当地也增加了20万亩的稻田面积。各地共查处景区价格违法案件88件,其中包括不按规定内容和方式明码标价32件、不落实门票价格减免优惠政策15件、通过捆绑销售变相涨价11件、擅自提高价格8件、自立项目收费5件、其它价格违法违规行为17件,共实施行政处罚2030万元。12月6日四川遂宁警方发布警情通报,公开征集线索。他强调,总结谋划好改革工作,对做好明年和今后改革工作具有重要意义,要总结经验、完善思路、突出重点,提高改革整体效能,扩大改革受益面,发挥好改革先导性作用,多推有利于增添经济发展动力的改革,多推有利于促进社会公平正义的改革,多推有利于增强人民群众获得感的改革,多推有利于调动广大干部群众积极性的改革。

EventBus 是一个基于观察者模式的事件订阅/发布框架。利用 EventBus 可以在不同模块之间,实现低耦合的消息通信。

作者:张旸|2019-09-11 10:55

 

一. 序

虽然现在互联网行业的就业形式「相当严峻」,张小胖还是成功跳槽涨薪。

入职第一天 Leader 说,“你刚来,这周先熟悉熟悉咱们的项目吧”。

张小胖熟练的用 Git pull 代码到本地,环境变量一通配置,终于把项目跑了起来,看着项目里的网络请求数据,居然全是靠 EventBus 分发,陷入了深深的沉思…

在子线程请求数据,再通过 EventBus 将数据分发到主线程,这是什么骚操作?这难道不会有问题吗?

虽然 EventBus 可以做到多模块之间低耦合的事件通信,可完全利用 EventBus 去做线程切换,解耦是解耦了,但靠谱的项目根本不会这么干。

不过既然聊到了 EventBus 的线程切换,那今天就深入聊聊当 EventBus 事件分发,遇上线程切换的时候,是如何处理的。以及使用的时候有什么需要注意的,大量的依赖 EventBus 的线程切换,会不会有效率问题。

二. EventBus 的线程切换

2.1 EventBus 切换线程

EventBus 是一个基于观察者模式的事件订阅/发布框架。利用 EventBus 可以在不同模块之间,实现低耦合的消息通信。

EventBus 诞生以来这么多年,在很多生产项目中都可以看到它的身影。而从更新日志可以看到,除了体积小,它还很稳定,这两年就没更新过,最后一次更新也只是因为支持所有的 JVM,让其使用范围不仅仅局限在 Android 上。

可谓是非常的稳定,稳定到让人有一种感觉,要是你使用 EventBus 出现了什么问题,那一定是你使用的方式不对。

EventBus 的使用方式,对于 Android 老司机来说,必然是不陌生的,相关资料太多,这里就不再赘述了。

在 Android 下,线程的切换是一个很常用而且很必须的操作,EventBus 除了可以订阅和发送消息之外,它还可以指定接受消息处理消息的线程。

也就是说,无论你 post() 消息时处在什么线程中,EventBus 都可以将消息分发到你指定的线程上去,听上去就感觉非常的方便。

不过无论怎么切换,无外乎几种情况:

  • UI 线程切子线程。
  • 子线程切 UI 线程。
  • 子线程切其他子线程。

在我们使用 EventBus 注册消息的时候,可以通过 @Subscribe 注解来完成注册事件, @Subscribe 中可以通过参数 threadMode 来指定使用那个线程来接收消息。

  1. @Subscribe(threadMode = ThreadMode.MAIN) 
  2. fun onEventTest(event:TestEvent){ 
  3.   / 处理事件 

threadMode 是一个 enum,有多种模式可供选择:

  1. POSTING,默认值,那个线程发就是那个线程收。
  2. MAIN,切换至主线程接收事件。
  3. MAIN_ORDERED,v3.1.1 中新增的属性,也是切换至主线程接收事件,但是和 MAIN 有些许区别,后面详细讲。
  4. BACKGROUND,确保在子线程中接收事件。细节就是,如果是主线程发送的消息,会切换到子线程接收,而如果事件本身就是由子线程发出,会直接使用发送事件消息的线程处理消息。
  5. ASYNC,确保在子线程中接收事件,但是和 BACKGROUND 的区别在于,它不会区分发送线程是否是子线程,而是每次都在不同的线程中接收事件。
  6. EventBus 的线程切换,主要涉及的方法就是 EventBus 的 postToSubscription()方法。
  1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { 
  2.   switch (subscription.subscriberMethod.threadMode) { 
  3.     case POSTING: 
  4.       invokeSubscriber(subscription, event); 
  5.       break; 
  6.     case MAIN: 
  7.       if (isMainThread) { 
  8.         invokeSubscriber(subscription, event); 
  9.       } else { 
  10.         mainThreadPoster.enqueue(subscription, event); 
  11.       } 
  12.       break; 
  13.     case MAIN_ORDERED: 
  14.       if (mainThreadPoster != null) { 
  15.         mainThreadPoster.enqueue(subscription, event); 
  16.       } else { 
  17.         / temporary: technically not correct as poster not decoupled from subscriber 
  18.         invokeSubscriber(subscription, event); 
  19.       } 
  20.       break; 
  21.     case BACKGROUND: 
  22.       if (isMainThread) { 
  23.         backgroundPoster.enqueue(subscription, event); 
  24.       } else { 
  25.         invokeSubscriber(subscription, event); 
  26.       } 
  27.       break; 
  28.     case ASYNC: 
  29.       asyncPoster.enqueue(subscription, event); 
  30.       break; 
  31.     default
  32.       throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); 
  33.   } 

可以看到,在 postToSubscription() 方法中,对我们配置的 threadMode 值进行了处理。

这段代码逻辑非常的简单,接下来我们看看它们执行的细节。

2.2 切换至主线程接收事件

想在主线程接收消息,需要配置 threadMode 为 MAIN。

  1. case MAIN: 
  2.   if (isMainThread) { 
  3.     invokeSubscriber(subscription, event); 
  4.   } else { 
  5.     mainThreadPoster.enqueue(subscription, event); 
  6.   } 

这一段的逻辑很清晰,判断是主线程就直接处理事件,如果是非主线程,就是用 mainThreadPoster 处理事件。

追踪 mainThreadPoster 的代码,具体的逻辑代码都在 HandlerPoster 类中,它实现了 Poster 接口,这就是一个普通的 Handler,只是它的 Looper 使用的是主线程的 「Main Looper」,可以将消息分发到主线程中。

为了提高效率,EventBus 在这里还做了一些小优化,值得我们借鉴学习。

为了避免频繁的向主线程 sendMessage(),EventBus 的做法是在一个消息里尽可能多的处理更多的消息事件,所以使用了 while 循环,持续从消息队列 queue 中获取消息。

同时为了避免长期占有主线程,间隔 10ms (maxMillisInsideHandleMessage = 10ms)会重新发送 sendMessage(),用于让出主线程的执行权,避免造成 UI 卡顿和 ANR。

MAIN 可以确保事件的接收,在主线程中,需要注意的是,如果事件就是在主线程中发送的,则使用 MAIN 会直接执行。为了让开发和可配置的程度更高,在 EventBus v3.1.1 新增了 MAIN_ORDERED,它不会区分当前线程,而是通通使用mainThreadPoster 来处理,也就是必然会走一遍 Handler 的消息分发。

当事件需要在主线程中处理的时候,要求不能执行耗时操作,这没什么好说的,另外对于 MAIN 或者 MAIN_ORDERED 的选择,就看具体的业务要求了。

2.3 切换至子线程执行

想要让消息在子线程中处理,可以配置 threadMode 为 BACKGROUND 或者AYSNC,他们都可以实现,但是也有一些区别。

先来看看 BACKGROUND,通过 postToSubscription() 中的逻辑可以看到,BACKGROUND会区分当前发生事件的线程,是否是主线程,非主线程则直接分发事件,如果是主线程,则 backgroundPoster 来分发事件。

  1. case BACKGROUND: 
  2.     if (isMainThread) { 
  3.         backgroundPoster.enqueue(subscription, event); 
  4.     } else { 
  5.         invokeSubscriber(subscription, event); 
  6.     } 
  7. break; 

BackgroundPoster 也实现了 Poster 接口,其中也维护了一个用链表实现的消息队列 PendingPostQueue,

在一些编码规范里就提到,不要直接创建线程,而是需要使用线程池。EventBus 也遵循这个规范,在 BackgroundPoster 中,就使用了 EventBus 的executorService 线程池对象去执行。

为了提高效率,EventBus 在处理 BackgroundPoster 时,也有一些小技巧值得我们学习。

可以看到,在 BackgroundPoster 中,处理主线程抛出的事件时,同一时刻只会存在一个线程,去循环从队列中,获取事件处理事件。

通过 synchronized 同步锁来保证队列数据的线程安全,同时利用 volatile 标识的 executorRunning 来保证不同线程下看到的执行状态是可见的。

既然 BACKGROUND 在处理任务的时候,只会使用一个线程,但是 EventBus 却用到了线程池,看似有点浪费。但是再继续了解 ASYNC 的实现,才知道怎么样是对线程池的充分利用。

和前面介绍的 threadMode 一样,大多数都对应了一个 Poster,而 ASYNC 对应的 Poster 是 AsyncPoster,其中并没有做任何特殊的处理,所有的事件,都是无脑的抛给 EventBus 的 executorService 这个线程池去处理,这也就保证了,无论如何发生事件的线程,和接收事件的线程,必然是不同的,也保证了一定会在子线程中处理事件。

  1. public void enqueue(Subscription subscription, Object event) { 
  2.     PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); 
  3.     queue.enqueue(pendingPost); 
  4.     eventBus.getExecutorService().execute(this); 

到这里应该就理解了 BACKGROUND 和 ASYNC ,虽然都可以保证在子线程中接收处理事件,但是内部实现是不同的。

BACKGROUND 同一时间,只会利用一个子线程,来循环从事件队列中获取事件并进行处理,也就是前面的事件的执行效率,会影响后续事件的执行。例如你分发了一个事件,使用的是 BACKGROUND 但是队列前面还有一个耗时操作,那你分发的这个事件,也必须等待队列前面的事件都处理完成才可以继续执行。所以如果你追求执行的效率,立刻马上就要执行的事件,可以使用 ASYNC。

那是不是都用 ASYNC 就好了?当然这种一揽子的决定都不会好,具体问题具体分析,ASYNC 也有它自己的问题。

ASYNC 会无脑的向线程池 executorService 发送任务,而这个线程池,如果你不配置的话,默认情况下使用的是 Executors 的 newCachedThreadPool() 创建的。

这里我又要说到编码规范了,不推荐使用 Executors 直接创建线程,之所以这样,其中一个原因在于线程池对任务的拒绝策略。newCachedThreadPool 则会创建一个无界队列,来存放线程池暂时无法处理的任务,说到无界队列,拍脑袋就能想到,当任务(事件)过多时,会出现的 OOM。

这也确实是 EventBus 在使用 ASYNC 时,真实存在的问题。

但是其实这里让开发者自己去配置,也很难配置一个合理的线程池的拒绝策略,拒绝时必然会放弃一些任务,也就是会放弃掉一些事件,任何放弃策略都是不合适的,这在 EventBus 的使用中,表现出来就是出现逻辑错误,该收到的事件,收不到了。所以你看,这里无界队列不合适,但是不用它呢也不合适,唯一的办法就是尽量少的使用 ASYNC,只在必要且合理的情况下,才去使用它。

三. 小结时刻

到这里基本上 EventBus 在分发事件时的线程切换,就讲清除了,很多资料里其实都写了他们可以切换线程,但是对于一些使用的细节,描述的并不清楚,正好借此文,把 EventBus 的线程切换的直接讲清除。

EventBus 也是简历上比较常见的高频词,我在面试时,也经常会问面试者,关于它是如何做到线程切换的问题。但是正因为它简单易用,其实很多时候我们都忽略了它的实现细节。

今天就到这里,小结一下:

1. EventBus 可以通过 threadMode 来配置接收事件的线程。

2. MAIN 和 MAIN_ORDERED 都会在主线程接收事件,区别在于是否区分,发生事件的线程是否是主线程。

3. BACKGROUND 确保在子线程中接收线程,它会通过线程池,使用一个线程循环处理所有的事件。所以事件的执行时机,会受到事件队列前面的事件处理效率的影响。

4. ASYNC 确保在子线程中接收事件,区别于 BACKGROUND,ASYNC 会每次向线程池中发送任务,通过线程池的调度去执行。但是因为线程池采用的是无界队列,会导致 ASYNC 待处理的事件太多时,会导致 OOM。

【本文为51CTO专栏作者“张旸”的原创稿件,转载请通过微信公众号联系作者获取授权】

太阳城娱乐网最快登入戳这里,看该作者更多好文

【编辑推荐】

  1. 太阳城娱乐网最快登入你真的懂网络分层模型吗?
  2. 太阳城娱乐网最快登入以太网数据传输原理你懂吗?
  3. 相比较于不安全的HTTP,HTTPS是怎么保证网络通信安全的
  4. 选择基于意图的网络的七个技巧
  5. 了解如何开始你的网络职业生涯
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
申博太阳城游戏帐号 太阳城娱乐网最快登入 www.7788shenbo.com 申博注册登入 菲律宾申博官网免费开户 申博现金网直营
www.360msc.com 申博现金网直营网 菲律宾申博官网直营网 申博游戏下载网址 99真人娱乐成登入 菲律宾太阳娱乐场登入
申博游戏直营网 太阳城申博登入 申博开户登入官网 www.83654.com 申博官网太阳城娱乐网 申博管理网直营