EventBus3.0 使用及源碼解析

2021-12-23 非著名程式設計師

【回復「1024」,送你一個特別推送】


投稿作者:一口仨饃
原文連結:http://blog.csdn.net/qq_17250009/article/details/51872731
特別聲明:本文為一口仨饃原創並授權發布,未經原作者允許請勿轉載,轉載請聯繫原作者。

叨了個叨

最近因為換工作的一些瑣事搞的我一個頭兩個大,也沒怎麼去學新東西,實在是有些愧疚。新項目用到了EventBus3.0,原來只是聽說EventBus的鼎鼎大名,一直沒仔細研究過。趁著周末有些時間,研究下代碼,也算沒有虛度光陰。

EventBus GitHub : https://github.com/greenrobot/EventBus

EventBus3.0簡介

EventBus是greenrobot出品的一個用於Android中事件發布/訂閱的庫。以前傳遞對象可能通過接口、廣播、文件等等,尤其像同一個Activity兩個Fragment之間採用接口傳遞對象,十分的麻煩,而且耦合度較高。使用EventBus之後,這些將不再是問題。盜用GiHub上EventBus的一張圖。

可以看到,發布者(Publisher)使用post()方法將Event發送到Event Bus,而後Event Bus自動將Event發送到多個訂閱者(Subcriber)。這裡需要注意兩個地方:(1)一個發布者可以對應多個訂閱者。(2)3.0以前訂閱者的訂閱方法為onEvent()、onEventMainThread()、onEventBackgroundThread()和onEventAsync()。在Event Bus3.0之後統一採用註解@Subscribe的形式,具體實現方式見下文。

EventBus3.0的使用

新建兩個Activity,花3s掃一下即可。代碼如下:



在MainActivity的onCreate()/onDestroy()中分別註冊/反註冊EventBus。然後寫了四個測試ThreadMode的方法,調用時機注釋的很清楚,就不贅述了。最後在SecondActivity的主線程和子線程中分別調用Post()方法,注意,這裡Post()方法的參數為Object類型,這也就意味著我們傳遞任何對象都是可以的,例如JavaBean、List<E>等等都是可以的,這裡為了方便演示直接傳遞了String。Log信息如下:


第一張圖中發布者發送線程為主線程,即Post thread = 1,在訂閱者收到消息時,ThreadMode = Main和ThreadMode = Posting的方法都在主線程調用,ASYNC和BACKGROUND都新開了線程。圖二對比注釋同理。

除此之外,Subscribe註解還支持priority和sticky屬性。priority設置接收者的優先級,默認值為0。優先級高的方法先被調用,在方法調用完成後可以調用EventBus.getDefault().cancelEventDelivery(event) ;終止優先級低的方法的調用。sticky為粘性事件,默認為關閉狀態。能夠收到訂閱之前發送到的最後一條消息,並且發送的方法不再是post()而是postSticky()。

EventBus3.0源碼解析

EventBus是Very的好用。耦合度大大的降低,而且代碼十分優雅。它是怎麼就做到了這麼優雅的呢?知其然,知其所以然。下面就開始一步步的分析。

註解標籤Subscribe

對註解不了解的同學可以看下這篇博客。


註解Subscribe在運行時解析,且只能加在METHOD上。其中有三個方法,threadMode()返回類型ThreadMode為枚舉類型,默認值為POSTING,sticky()默認返回false,priority()默認返回0。

1. Register流程EventBus#getDefault()

EventBus採用雙重校驗鎖設計為一個單例模式,奇怪的在於雖然設計為單例模式,但是構造方法確實public類型,這不是坑爹嘛!難道greenrobot在設計EventBus獲取實例方法的時候在打LOL,一不小心打錯了?原來啊,EventBus默認支持一條事件總線,通常是通過getDefault()方法獲取EventBus實例,但也能通過直接new EventBus這種最簡單的方式獲取多條事件總線,彼此之間完全分開。設計之思想不禁讓人拍案叫絕。

EventBus#register()

首先得到訂閱者的報名.類名,即哪個具體類註冊。然後調用subscriberMethodFinder.findSubscriberMethods(subscriberClass),從方法名和返回值來看,findSubscriberMethods()的作用應該是遍歷查找訂閱者中所有的訂閱方法。

SubscriberMethodFinder#findSubscriberMethods()

注意subscriberMethods.isEmpty(),如果註冊了EventBus,但卻沒有使用註解Subscribe是會出現EventBusException異常的。下面跟進findUsingInfo()方法。

SubscriberMethodFinder#findUsingInfo()

findState.subscriberInfo默認null,那麼就進入到findUsingReflectionInSingleClass(findState),先看下這個方法,等下還要返回來看。

SubscriberMethodFinder#findUsingReflectionInSingleClass()

接下來返回SubscriberMethodFinder#findUsingInfo()接著看,在findUsingInfo()中循環執行完後return getMethodsAndRelease(findState)

在getMethodsAndRelease()中將findState置空,存放進FIND_STATE_POOL數組,最後返回findState.subscriberMethods。返回EventBus#register()。

EventBus#register()


調用SubscriberMethodFinder#findSubscriberMethods()後,以List<SubscriberMethod>形式返回了訂閱者所有的訂閱事件。然後遍歷執行subscribe()方法。看樣子應該是遍歷List<SubscriberMethod>,然後將訂閱者和訂閱事件綁定。沒撒好說的,跟進subscribe()

EventBus#subscribe()

EventBus#Register()其實只做了三件事:

1. 查找訂閱者所有的訂閱事件

2. 將訂閱事件作為key,所有訂閱了此訂閱事件的訂閱者作為value存放進subscriptionsByEventType

3. 將訂閱者作為key,訂閱者的所有訂閱事件作為value存放進typesBySubscriber

至此,EventBus.getDefault().register(this)流程完畢。

2. Post流程EventBus#getDefault()

獲取EventBus實例。和Register流程中一樣,不再贅述。

EventBus#post()

上面代碼中currentPostingThreadState為ThreadLocal<PostingThreadState>對象,對ThreadLocal<>機制不了解的同學,可以查看這篇博客。下面跟進postSingleEvent()方法。

EventBus#postSingleEvent()

EventBus#lookupAllEventTypes()

現在假設傳遞的數據為Person類,而Person類實現了IPerson接口。通過上面的分析可以得出結論:在傳遞對象(Person)的時候,訂閱事件中參數為被傳遞對象的所有父類訂閱事件(IPerson)也都會被調用。筆者已經驗證通過,感興趣的同學可以再驗證一下。

EventBus#postSingleEventForEventType()

在EventBus#register()最後總結道:將訂閱事件作為key,所有訂閱了此訂閱事件的訂閱者作為value存放進subscriptionsByEventType。這裡就依據訂閱事件然後查找對應所有的訂閱者。注意:由於遍歷訂閱事件參數所有父類的原因,一個訂閱事件的Post第一次執行postToSubscription()時,subscription參數,遍歷時為訂閱事件的訂閱者。之後再調用postToSubscription()時,subscription參數都為訂閱時間父類的訂閱者。而event參數則一直是訂閱事件中的唯一參數(最底層子類)。

EventBus#postToSubscription()

看到這裡差不多可以鬆口氣,終於要分發調用訂閱者的訂閱事件了!寫了整整一下午,容我抽支煙再。

首先根據ThreadMode確定分發類型。這裡以最常用的Main為例,其餘兩個Poster同理。如果是isMainThread=true,那麼直接調用invokeSubscriber(),否則調用mainThreadPoster.enqueue()。下面分別解釋這兩種情況。

EventBus#invokeSubscriber()

沒撒好說的,直接反射調用訂閱者的訂閱事件。注意:參數event是子類對象,就算調用訂閱事件中唯一參數是參數的父類,那麼傳遞的仍然是子類對象。筆者已經驗證,感興趣的同學可以自行驗證。然後查看HandlerPoster#enqueue()。

HandlerPoster#enqueue()

EventBus#invokeSubscriber()

至此,訂閱者在相應線程調用訂閱事件完成,EventBus.getDefault().Post()流程完畢。

EventBus#Post()也只做了三件事

1. 根據訂閱事件在subscriptionsByEventType中查找相應的訂閱者

2. 分發訂閱者的訂閱事件調用線程

2. 通過反射調用訂閱者的訂閱事件

3. unregister流程EventBus#getDefault()

獲取EventBus實例。和Register流程中一樣,不再贅述。

EventBus#unregister()

EventBus#unsubscribeByEventType()

在EventBus#register()最後總結道:

將訂閱事件作為key,所有訂閱了此訂閱事件的訂閱者作為value存放進subscriptionsByEventType。

將訂閱者作為key,訂閱者的所有訂閱事件作為value存放進typesBySubscriber。

現在要反註冊咯。移除相應的key、value即可。EventBus3.0的使用及源碼解析到此結束,Have a nice day~

● ● ●

掘金是一個高質量的技術社區,從 RxJava 到 Android Studio,性能優化到優秀開源庫,讓你不錯過 Android 開發的每一個技術乾貨。長按圖片二維碼識別或者各大應用市場搜索「掘金」,技術乾貨盡在掌握中。

相關焦點

  • Android EventBus3 源碼解析 把控事件總線
    由於EventBus3.0.0(後文統一簡寫成「EventBus」)較以前版本無論是使用性能還是使用方式都有較大差異,本文分析基於最新的3.0.0版本。使用:1.1 項目引入EventBus(Gradle)打開對應模塊的build.gradle,在dependencies中加入EventBus依賴:compile 'org.greenrobot:eventbus:3.0.0'EventBus3.0最大的優化就是通過EventBusAnnotationProcessor在編譯時生成索引,
  • EventBus源碼詳解(一)
    粘性事件可以當作普通事件使用。別急,聽我慢慢道來~EventBus 3.0之前的版本是沒有索引的,檢索訂閱方法是通過反射獲取的。我們都知道反射的效率令人堪憂,如果頻繁地調用的話,肯定會對程序的性能造成影響。而greenrobot也意識到這個問題,所以在EventBus 3.0版本新增一個索引的功能,它主要是通過在編譯期處理,生成訂閱者和訂閱方法的對應關係並緩存起來,從而在程序運行時能快速索引。
  • 面試題之---EventBus源碼解析
    3.8.通過流(本地文件)傳遞八大基本數據類型和實現Serializable接口的數據類型3.9.通過基類中的屬性或者方法屬性: 基類公有屬性  在某個子類中賦值   其他子類中都能使用方法: 子類調用父類的某個方法給父類某個屬性賦值  另外一個子類通過父類的另一個公有方法獲取這個值(這個方法把值返回)(二)基本使用先訂閱,後發布
  • EventBus 流程解析
    作者丨老王頭碎碎念https://www.jianshu.com/p/c64b2315db3cEventBus 源碼解析
  • EventBus 原理深度解析
    如果是同一個jvm裡面通知的話,就可以使用EventBus。由於EventBus使用起來簡單、便捷,因此,工作中會經常用到。深入理解該框架的原理就很有必要。二、框架解析2.1、組織結構eventbus的組織結構如下:
  • EventBus—事件總線
    Vue EventBus 源碼解析VUE中EventBus可以用來進行任何組件之間的通信。EventBus可以當成一個管道,這個管道兩端可以接好多組件,兩端的任何一個組件都可以進行通信。這個管道就是Vue實例,實例中有四個跟事件派發相關的方法$on $off $emit $once。
  • EventBus 3.0+ 源碼詳解(史上最詳細圖文講解)(上)
    本文講解的是 'org.greenrobot:eventbus:3.1.1' 最新版,和其他版本有細微差別,思想都是一樣的。);            if (size > 0) {                PendingPost pendingPost = pendingPostPool.remove(size - 1);                pendingPost.event = event;                pendingPost.subscription
  • 玩轉EventBus,詳解其使用
    在EventBus中,使用約定來指定事件訂閱者以簡化使用。即所有事件訂閱都都是以onEvent開頭的函數,具體來說,函數的名字是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync這四個,這個和ThreadMode(下面講)有關。Publisher:事件發布者,用於通知 Subscriber 有事件發生。
  • .NET編程之事件總線(Event Bus)知多少 (2)
    3、發現反射問題基於以上的簡單回顧,我們可以發現Alpha版本事件總線的成功離不開反射的支持。從動態綁定到動態觸發,都是反射在默默的處理著業務邏輯。如果我們只是簡單學習了解事件總線,使用反射無可厚非。但如果在實際的項目中,使用反射卻不是一個很明智的行為,因為其性能問題。
  • @EventListener--- Spring源碼從入門到精通(三十)
    上篇文章介紹實現ApplicationListener接口實現spring事件監聽:ApplicationListener--- Spring源碼從入門到精通
  • Jetpack源碼解析--ViewModel基本使用及源碼解析
    1.背景Jetpack源碼解析系列文章:1. Android_Jetpack組件---Naviagtion源碼解析2. Jetpack源碼解析—Navigation為什麼切換Fragment會重繪?3. Jetpack源碼解析---用Lifecycles管理生命周期4.
  • Vue組件的通信--eventBus
    但是對於具有簡單體系結構的應用程式來說,使用事件在組件之間進行通信就足夠了。為此,我們可以創建一個快速的解決方案並實現EventBus,也稱作vue的中央事件總線,適用於跨級或兄弟組件。EventBus允許我們在一個組件中發出一個事件,而在另一個組件中偵聽該事件。本示例將說明如何在Vue.js應用程式中執行此操作。
  • 3. Jetpack源碼解析---用Lifecycles管理生命周期
    背景上一篇我門對Jetpack組件中的Navigation做了介紹,並且對其做了源碼分析,相信看過之後已經對此有了一定的了解,本篇文章我們會對Lifecycles進行使用及源碼的介紹,還沒看上篇的可以看一下:系列文章:1. Android_Jetpack組件---Naviagtion源碼解析2.
  • OkHttp3源碼解析(整體流程)
    前言今天主要講一下OkHttp3
  • Vue.js 3.x 源碼解析先導
    前言2018 年 6 月我在慕課網發布了 Vue.js 2.x 的源碼解析課程 《Vue.js 源碼全方位深入解析》,同時也開源了課程配套電子書。時隔一年多,Vue 官方也開源了 Vue.js 3.x,那麼在不久的將來,我也會系統化地做 Vue.js 3.x 的源碼分析,同時更新我的這門課程視頻以及電子書。
  • Java 程式設計師眼裡的 Linux 內核 —— wait_event 源碼分析
    原文開始:看 Linux 的 wait_event 源碼時,聯想到我們平時經常用得比較多的 wait/notify、double-check 和 volatile,突然意識 wait_event 簡簡單單幾行代碼的背後,涉及的知識點其實非常豐富。本篇文章我們就一起了來探索它背後的知識,然後嘗試著和我們的日常開發關聯起來。
  • 你不可不知的前端進階知識:Vue事件總線(EventBus)使用詳細介紹
    在Vue中可以使用EventBus 來作為溝通橋梁的概念,就像是所有組件共用相同的事件中心,可以向該中心註冊發送事件或接收事件,所以組件都可以上下平行地通知其他組件,但也就是太方便所以若使用不慎,就會造成難以維護的「災難」,因此才需要更完善的Vuex作為狀態管理中心,將通知的概念上升到共享狀態層次。
  • 從 Event Loop 角度解讀 Vue NextTick 源碼
    ,nextTick 方法藉助了瀏覽器的 event loop 事件循環做到了異步更新。學會 nextTick 原理幫助定位 BUG , 使用 Vue 會更加靈活。什麼是 event loop先看一張圖 (來自 mr.z 大佬)
  • 全套源碼丨Cocos Creator 3.0 開發 io 類遊戲關鍵技術點詳解
    Cocos 引擎官方於上周正式推出一款 io 類玩法遊戲源碼《奔跑吧小仙女》。本項目源碼包裡包含了完整策劃文檔、項目源碼、美術源文件,且官方支持免費提供微信小遊戲上線源碼授權服務。《奔跑吧小仙女》在不使用物理的基礎上進行開發。同時,我們也配置了完整的教程,包括如何啟動場景、攝像機跟隨、水面和天空球的設置、玩家與 AI 的控制,以及如何加載數據,相關配置文件等。除此之外我們還有地圖編輯器,供玩家擴展使用。
  • ZooKeeper源碼學習筆記--client端解析
    ZooKeeper是一個相對簡單的分布式協調服務,通過閱讀源碼我們能夠更進一步的清楚分布式的原理。ZooKeeper 3.4.9在bin/zkCli.sh中,我們看到client端的真實入口其實是一個org.apache.zookeeper.ZooKeeperMain的Java類通過源碼走讀,看到在ZooKeeperMain中主要由兩部分構成