【回復「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的形式,具體實現方式見下文。
新建兩個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。
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#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 開發的每一個技術乾貨。長按圖片二維碼識別或者各大應用市場搜索「掘金」,技術乾貨盡在掌握中。