自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

“入職接手舊項(xiàng)目,所有網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)通過 EventBus 分發(fā),嚇得我想離職...”

企業(yè)動(dòng)態(tài)
EventBus 是一個(gè)基于觀察者模式的事件訂閱/發(fā)布框架。利用 EventBus 可以在不同模塊之間,實(shí)現(xiàn)低耦合的消息通信。

 [[276585]]

一. 序

雖然現(xiàn)在互聯(lián)網(wǎng)行業(yè)的就業(yè)形式「相當(dāng)嚴(yán)峻」,張小胖還是成功跳槽漲薪。

入職第一天 Leader 說,“你剛來,這周先熟悉熟悉咱們的項(xiàng)目吧”。

張小胖熟練的用 Git pull 代碼到本地,環(huán)境變量一通配置,終于把項(xiàng)目跑了起來,看著項(xiàng)目里的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),居然全是靠 EventBus 分發(fā),陷入了深深的沉思…

在子線程請(qǐng)求數(shù)據(jù),再通過 EventBus 將數(shù)據(jù)分發(fā)到主線程,這是什么騷操作?這難道不會(huì)有問題嗎?

雖然 EventBus 可以做到多模塊之間低耦合的事件通信,可完全利用 EventBus 去做線程切換,解耦是解耦了,但靠譜的項(xiàng)目根本不會(huì)這么干。

不過既然聊到了 EventBus 的線程切換,那今天就深入聊聊當(dāng) EventBus 事件分發(fā),遇上線程切換的時(shí)候,是如何處理的。以及使用的時(shí)候有什么需要注意的,大量的依賴 EventBus 的線程切換,會(huì)不會(huì)有效率問題。

二. EventBus 的線程切換

2.1 EventBus 切換線程

EventBus 是一個(gè)基于觀察者模式的事件訂閱/發(fā)布框架。利用 EventBus 可以在不同模塊之間,實(shí)現(xiàn)低耦合的消息通信。

 

EventBus 誕生以來這么多年,在很多生產(chǎn)項(xiàng)目中都可以看到它的身影。而從更新日志可以看到,除了體積小,它還很穩(wěn)定,這兩年就沒更新過,最后一次更新也只是因?yàn)橹С炙械? JVM,讓其使用范圍不僅僅局限在 Android 上。

可謂是非常的穩(wěn)定,穩(wěn)定到讓人有一種感覺,要是你使用 EventBus 出現(xiàn)了什么問題,那一定是你使用的方式不對(duì)。

EventBus 的使用方式,對(duì)于 Android 老司機(jī)來說,必然是不陌生的,相關(guān)資料太多,這里就不再贅述了。

在 Android 下,線程的切換是一個(gè)很常用而且很必須的操作,EventBus 除了可以訂閱和發(fā)送消息之外,它還可以指定接受消息處理消息的線程。

也就是說,無論你 post() 消息時(shí)處在什么線程中,EventBus 都可以將消息分發(fā)到你指定的線程上去,聽上去就感覺非常的方便。

不過無論怎么切換,無外乎幾種情況:

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

在我們使用 EventBus 注冊(cè)消息的時(shí)候,可以通過 @Subscribe 注解來完成注冊(cè)事件, @Subscribe 中可以通過參數(shù) threadMode 來指定使用那個(gè)線程來接收消息。

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

threadMode 是一個(gè) enum,有多種模式可供選擇:

  1. POSTING,默認(rèn)值,那個(gè)線程發(fā)就是那個(gè)線程收。
  2. MAIN,切換至主線程接收事件。
  3. MAIN_ORDERED,v3.1.1 中新增的屬性,也是切換至主線程接收事件,但是和 MAIN 有些許區(qū)別,后面詳細(xì)講。
  4. BACKGROUND,確保在子線程中接收事件。細(xì)節(jié)就是,如果是主線程發(fā)送的消息,會(huì)切換到子線程接收,而如果事件本身就是由子線程發(fā)出,會(huì)直接使用發(fā)送事件消息的線程處理消息。
  5. ASYNC,確保在子線程中接收事件,但是和 BACKGROUND 的區(qū)別在于,它不會(huì)區(qū)分發(fā)送線程是否是子線程,而是每次都在不同的線程中接收事件。
  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() 方法中,對(duì)我們配置的 threadMode 值進(jìn)行了處理。

這段代碼邏輯非常的簡單,接下來我們看看它們執(zhí)行的細(xì)節(jié)。

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 類中,它實(shí)現(xiàn)了 Poster 接口,這就是一個(gè)普通的 Handler,只是它的 Looper 使用的是主線程的 「Main Looper」,可以將消息分發(fā)到主線程中。

為了提高效率,EventBus 在這里還做了一些小優(yōu)化,值得我們借鑒學(xué)習(xí)。

 

為了避免頻繁的向主線程 sendMessage(),EventBus 的做法是在一個(gè)消息里盡可能多的處理更多的消息事件,所以使用了 while 循環(huán),持續(xù)從消息隊(duì)列 queue 中獲取消息。

同時(shí)為了避免長期占有主線程,間隔 10ms (maxMillisInsideHandleMessage = 10ms)會(huì)重新發(fā)送 sendMessage(),用于讓出主線程的執(zhí)行權(quán),避免造成 UI 卡頓和 ANR。

MAIN 可以確保事件的接收,在主線程中,需要注意的是,如果事件就是在主線程中發(fā)送的,則使用 MAIN 會(huì)直接執(zhí)行。為了讓開發(fā)和可配置的程度更高,在 EventBus v3.1.1 新增了 MAIN_ORDERED,它不會(huì)區(qū)分當(dāng)前線程,而是通通使用mainThreadPoster 來處理,也就是必然會(huì)走一遍 Handler 的消息分發(fā)。

當(dāng)事件需要在主線程中處理的時(shí)候,要求不能執(zhí)行耗時(shí)操作,這沒什么好說的,另外對(duì)于 MAIN 或者 MAIN_ORDERED 的選擇,就看具體的業(yè)務(wù)要求了。

2.3 切換至子線程執(zhí)行

想要讓消息在子線程中處理,可以配置 threadMode 為 BACKGROUND 或者AYSNC,他們都可以實(shí)現(xiàn),但是也有一些區(qū)別。

先來看看 BACKGROUND,通過 postToSubscription() 中的邏輯可以看到,BACKGROUND會(huì)區(qū)分當(dāng)前發(fā)生事件的線程,是否是主線程,非主線程則直接分發(fā)事件,如果是主線程,則 backgroundPoster 來分發(fā)事件。

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

BackgroundPoster 也實(shí)現(xiàn)了 Poster 接口,其中也維護(hù)了一個(gè)用鏈表實(shí)現(xiàn)的消息隊(duì)列 PendingPostQueue,

在一些編碼規(guī)范里就提到,不要直接創(chuàng)建線程,而是需要使用線程池。EventBus 也遵循這個(gè)規(guī)范,在 BackgroundPoster 中,就使用了 EventBus 的executorService 線程池對(duì)象去執(zhí)行。

為了提高效率,EventBus 在處理 BackgroundPoster 時(shí),也有一些小技巧值得我們學(xué)習(xí)。

 

可以看到,在 BackgroundPoster 中,處理主線程拋出的事件時(shí),同一時(shí)刻只會(huì)存在一個(gè)線程,去循環(huán)從隊(duì)列中,獲取事件處理事件。

通過 synchronized 同步鎖來保證隊(duì)列數(shù)據(jù)的線程安全,同時(shí)利用 volatile 標(biāo)識(shí)的 executorRunning 來保證不同線程下看到的執(zhí)行狀態(tài)是可見的。

既然 BACKGROUND 在處理任務(wù)的時(shí)候,只會(huì)使用一個(gè)線程,但是 EventBus 卻用到了線程池,看似有點(diǎn)浪費(fèi)。但是再繼續(xù)了解 ASYNC 的實(shí)現(xiàn),才知道怎么樣是對(duì)線程池的充分利用。

和前面介紹的 threadMode 一樣,大多數(shù)都對(duì)應(yīng)了一個(gè) Poster,而 ASYNC 對(duì)應(yīng)的 Poster 是 AsyncPoster,其中并沒有做任何特殊的處理,所有的事件,都是無腦的拋給 EventBus 的 executorService 這個(gè)線程池去處理,這也就保證了,無論如何發(fā)生事件的線程,和接收事件的線程,必然是不同的,也保證了一定會(huì)在子線程中處理事件。

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

到這里應(yīng)該就理解了 BACKGROUND 和 ASYNC ,雖然都可以保證在子線程中接收處理事件,但是內(nèi)部實(shí)現(xiàn)是不同的。

BACKGROUND 同一時(shí)間,只會(huì)利用一個(gè)子線程,來循環(huán)從事件隊(duì)列中獲取事件并進(jìn)行處理,也就是前面的事件的執(zhí)行效率,會(huì)影響后續(xù)事件的執(zhí)行。例如你分發(fā)了一個(gè)事件,使用的是 BACKGROUND 但是隊(duì)列前面還有一個(gè)耗時(shí)操作,那你分發(fā)的這個(gè)事件,也必須等待隊(duì)列前面的事件都處理完成才可以繼續(xù)執(zhí)行。所以如果你追求執(zhí)行的效率,立刻馬上就要執(zhí)行的事件,可以使用 ASYNC。

那是不是都用 ASYNC 就好了?當(dāng)然這種一攬子的決定都不會(huì)好,具體問題具體分析,ASYNC 也有它自己的問題。

ASYNC 會(huì)無腦的向線程池 executorService 發(fā)送任務(wù),而這個(gè)線程池,如果你不配置的話,默認(rèn)情況下使用的是 Executors 的 newCachedThreadPool() 創(chuàng)建的。

這里我又要說到編碼規(guī)范了,不推薦使用 Executors 直接創(chuàng)建線程,之所以這樣,其中一個(gè)原因在于線程池對(duì)任務(wù)的拒絕策略。newCachedThreadPool 則會(huì)創(chuàng)建一個(gè)無界隊(duì)列,來存放線程池暫時(shí)無法處理的任務(wù),說到無界隊(duì)列,拍腦袋就能想到,當(dāng)任務(wù)(事件)過多時(shí),會(huì)出現(xiàn)的 OOM。

這也確實(shí)是 EventBus 在使用 ASYNC 時(shí),真實(shí)存在的問題。

 

但是其實(shí)這里讓開發(fā)者自己去配置,也很難配置一個(gè)合理的線程池的拒絕策略,拒絕時(shí)必然會(huì)放棄一些任務(wù),也就是會(huì)放棄掉一些事件,任何放棄策略都是不合適的,這在 EventBus 的使用中,表現(xiàn)出來就是出現(xiàn)邏輯錯(cuò)誤,該收到的事件,收不到了。所以你看,這里無界隊(duì)列不合適,但是不用它呢也不合適,唯一的辦法就是盡量少的使用 ASYNC,只在必要且合理的情況下,才去使用它。

三. 小結(jié)時(shí)刻

到這里基本上 EventBus 在分發(fā)事件時(shí)的線程切換,就講清除了,很多資料里其實(shí)都寫了他們可以切換線程,但是對(duì)于一些使用的細(xì)節(jié),描述的并不清楚,正好借此文,把 EventBus 的線程切換的直接講清除。

EventBus 也是簡歷上比較常見的高頻詞,我在面試時(shí),也經(jīng)常會(huì)問面試者,關(guān)于它是如何做到線程切換的問題。但是正因?yàn)樗唵我子?,其?shí)很多時(shí)候我們都忽略了它的實(shí)現(xiàn)細(xì)節(jié)。

今天就到這里,小結(jié)一下:

1. EventBus 可以通過 threadMode 來配置接收事件的線程。

2. MAIN 和 MAIN_ORDERED 都會(huì)在主線程接收事件,區(qū)別在于是否區(qū)分,發(fā)生事件的線程是否是主線程。

3. BACKGROUND 確保在子線程中接收線程,它會(huì)通過線程池,使用一個(gè)線程循環(huán)處理所有的事件。所以事件的執(zhí)行時(shí)機(jī),會(huì)受到事件隊(duì)列前面的事件處理效率的影響。

4. ASYNC 確保在子線程中接收事件,區(qū)別于 BACKGROUND,ASYNC 會(huì)每次向線程池中發(fā)送任務(wù),通過線程池的調(diào)度去執(zhí)行。但是因?yàn)榫€程池采用的是無界隊(duì)列,會(huì)導(dǎo)致 ASYNC 待處理的事件太多時(shí),會(huì)導(dǎo)致 OOM。

【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】 

戳這里,看該作者更多好文

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2017-01-15 07:55:19

Swift蘋果離職

2021-04-12 05:55:29

緩存數(shù)據(jù)Axios

2023-11-20 21:56:57

入職微軟

2021-09-26 10:15:44

離職程序員復(fù)盤

2021-03-27 22:21:48

HTTPPython數(shù)據(jù)

2018-07-16 16:39:00

數(shù)據(jù)

2018-08-09 10:14:25

程序猿競業(yè)限制補(bǔ)償金

2025-02-06 16:33:04

2023-05-04 10:05:30

離職谷歌

2020-10-20 09:51:51

Vue 3 的組合

2020-10-20 09:30:13

Vue 3 API 數(shù)據(jù)

2020-11-03 07:48:47

當(dāng)AI入職FBI

2021-05-17 08:11:44

MySQL數(shù)據(jù)庫索引

2024-02-06 14:09:00

微服務(wù)項(xiàng)目集成

2018-01-15 15:00:06

工程師項(xiàng)目設(shè)計(jì)師

2020-02-22 14:49:30

畢業(yè)入職半年感受

2020-11-13 11:01:33

工具人互聯(lián)網(wǎng)技術(shù)

2023-07-06 09:01:33

2022-05-25 11:17:33

日志系統(tǒng)維護(hù)

2024-07-24 20:01:03

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)