EventBus用法及源碼解析php
目錄介紹html
1.EventBus簡介git
1.1 EventBus的三要素github
1.2 EventBus的四種ThreadMode(線程模型)緩存
1.3 EventBus怎麼調用併發
2.EventBus使用app
2.1 最簡單的使用async
3.EventBus註冊源碼解析ide
3.1 EventBus.getDefault()獲取對象函數
3.2 register(this)註冊源碼解析
3.2.1 首先看register(this)源碼
3.2.2 接下來看findSubscriberMethods(subscriberClass)裏面的源碼
3.2.3 接下來看findUsingInfo(subscriberClass)源碼
3.3 查找完全部的訂閱方法後便開始對全部的訂閱方法進行註冊
3.3.1 subscribe(subscriber, subscriberMethod);
3.3.2 訂閱者的註冊過程
3.3.3 流程圖
4.EventBus事件分發解析
4.1 從post方法入手
4.2 什麼是PostingThreadState?
4.3 PostingThreadState怎麼得到?
4.4 來看看postSingleEvent方法裏作了什麼
4.5 接下來看看postSingleEventForEventType方法
4.6 接下來看看postToSubscription方法
4.7 整個流程圖
4.8 總結一下整個事件分發的過程
5.EventBus取消註冊解析
5.1 unregister(this)方法入手
5.2 再來看看unsubscribeByEventType(subscriber, eventType)
5.3 取消註冊流程圖
5.4 總結一下取消註冊的過程
6.總結一下EventBus的工做原理
6.1 訂閱邏輯
6.2 事件發送邏輯
6.3 取消邏輯
6.4 利與弊
7.其餘介紹
7.1 參考文檔
7.2 其餘
0.本人寫的綜合案例
模塊:新聞,音樂,視頻,圖片,唐詩宋詞,快遞,天氣,記事本,閱讀器等等
接口:七牛,阿里雲,天行,乾貨集中營,極速數據,追書神器等等
持續更新目錄說明:http://www.jianshu.com/p/5301...
1.EventBus簡介
1.1 EventBus的三要素
Event:事件
能夠是任意類型的對象。
Subscriber:事件訂閱者
在EventBus3.0以前,消息處理的方法只能限定於onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他們分別表明四種線程模型。
在EventBus3.0以後,事件處理的方法能夠隨便取名,可是須要添加一個註解@Subscribe,而且要指定線程模型(默認爲POSTING),四種線程模型下面會講到。
Publisher:事件發佈者
能夠在任意線程任意位置發送事件,直接調用EventBus的post(Object)方法。能夠本身實例化EventBus對象,但通常使用EventBus.getDefault()就行了,根據post函數參數的類型,會自動調用訂閱相應類型事件的函數。
1.2 EventBus的四種ThreadMode(線程模型)
POSTING(默認):
若是使用事件處理函數指定了線程模型爲POSTING,那麼該事件在哪一個線程發佈出來的,事件處理函數就會在這個線程中運行,也就是說發佈事件和接收事件在同一個線程。在線程模型爲POSTING的事件處理函數中儘可能避免執行耗時操做,由於它會阻塞事件的傳遞,甚至有可能會引發ANR。
MAIN:
事件的處理會在UI線程中執行。事件處理時間不能太長,長了會ANR的。
BACKGROUND:
若是事件是在UI線程中發佈出來的,那麼該事件處理函數就會在新的線程中運行,若是事件原本就是子線程中發佈出來的,那麼該事件處理函數直接在發佈事件的線程中執行。在此事件處理函數中禁止進行UI更新操做。
ASYNC:
不管事件在哪一個線程發佈,該事件處理函數都會在新建的子線程中執行,一樣,此事件處理函數中禁止進行UI更新操做。
1.3 EventBus怎麼調用**
代碼以下: EventBus.getDefault().post(param);
調用原理簡單理解爲:
一句話,你也能夠叫發佈,只要把這個param發佈出去,EventBus會在它內部存儲的方法中,進行掃描,找到參數匹配的,就使用反射進行調用。
撇開專業術語:其實EventBus就是在內部存儲了一堆onEvent開頭的方法,而後post的時候,根據post傳入的參數,去找到匹配的方法,反射調用之。
它內部使用了Map進行存儲,鍵就是參數的Class類型。知道是這個類型,那麼你以爲根據post傳入的參數進行查找仍是個事麼?
2.EventBus使用
2.1 最簡單的使用
2.1.1 自定義一個事件類
public class MessageEvent { }
2.1.2 在須要訂閱事件的地方註冊事件
EventBus.getDefault().register(this);
2.1.3 發送事件
EventBus.getDefault().post(messageEvent);
2.1.4 處理事件
3.0以後, 消息處理的方法能夠隨便取名
問題: (threadMode = ThreadMode.MAIN)是作什麼用的??
須要添加一個註解@Subscribe,而且要指定線程模型
若是沒有添加,那就是默認爲POSTING
@Subscribe(threadMode = ThreadMode.MAIN) public void Hhhh(MessageEvent messageEvent) { ... }
2.1.5 取消事件訂閱
EventBus.getDefault().unregister(this);
3.EventBus註冊源碼解析
3.1 EventBus.getDefault()獲取對象
a.先看源碼:
/** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
b.分析
單例模式, 使用了雙重判斷的方式,防止併發的問題,還能極大的提升效率
3.2 register(this)註冊源碼解析
3.2.1.首先看register(this)源碼
public void register(Object subscriber) { //首先獲取訂閱者的類對象 Class<?> subscriberClass = subscriber.getClass(); //用 subscriberMethodFinder 提供的方法,找到在 subscriber 這個類裏面訂閱的內容。 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { // for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
3.2.2.接下來看findSubscriberMethods(subscriberClass)裏面的源碼
該方法的做用其實就是從訂閱類中獲取全部的訂閱方法信息
findSubscriberMethods找出一個SubscriberMethod的集合,也就是傳進來的訂閱者全部的訂閱的方法,接下來遍歷訂閱者的訂閱方法來完成訂閱者的訂閱操做。對於SubscriberMethod(訂閱方法)類中,主要就是用保存訂閱方法的Method對象、線程模式、事件類型、優先級、是不是粘性事件等屬性。
源碼分析:首先從緩存中查找,若是找到了就立馬返回。若是緩存中沒有的話,則根據 ignoreGeneratedIndex 選擇如何查找訂閱方法,ignoreGeneratedIndex屬性表示是否忽略註解器生成的MyEventBusIndex。最後,找到訂閱方法後,放入緩存,以避免下次繼續查找。ignoreGeneratedIndex 默認就是false,能夠經過EventBusBuilder來設置它的值。咱們在項目中常常經過EventBus單例模式來獲取默認的EventBus對象,也就是ignoreGeneratedIndex爲false的狀況,這種狀況調用了findUsingInfo方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //首先從緩存中讀取 List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //是否忽略註解器生成的MyEventBusIndex類 if (ignoreGeneratedIndex) { //利用反射來獲取訂閱類中的訂閱方法信息 subscriberMethods = findUsingReflection(subscriberClass); } else { //從註解器生成的MyEventBusIndex類中得到訂閱類的訂閱方法信息 subscriberMethods = findUsingInfo(subscriberClass); } //在得到subscriberMethods之後,若是訂閱者中不存在@Subscribe註解而且爲public的訂閱方法,則會拋出異常。 if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //保存進緩存 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } } //METHOD_CACHE,是一個map集合,鍵是class類型 Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
3.2.3 接下來看findUsingInfo(subscriberClass)源碼
經過getSubscriberInfo方法來獲取訂閱者信息。在咱們開始查找訂閱方法的時候並無忽略註解器爲咱們生成的索引MyEventBusIndex,若是咱們經過EventBusBuilder配置了MyEventBusIndex,便會獲取到subscriberInfo,調用subscriberInfo的getSubscriberMethods方法即可以獲得訂閱方法相關的信息,這個時候就不在須要經過註解進行獲取訂閱方法。若是沒有配置MyEventBusIndex,便會執行findUsingReflectionInSingleClass方法,將訂閱方法保存到findState中。最後再經過getMethodsAndRelease方法對findState作回收處理並反回訂閱方法的List集合。
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //獲取訂閱者信息,沒有配置MyEventBusIndex返回null findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { //經過反射來查找訂閱方法 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
3.3 查找完全部的訂閱方法後便開始對全部的訂閱方法進行註冊
3.3.1 回到
register(this)這個方法
3.3.2 訂閱者的註冊過程
訂閱的代碼主要就作了兩件事,第一件事是將咱們的訂閱方法和訂閱者封裝到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是咱們投遞訂閱事件的時候,就是根據咱們的EventType找到咱們的訂閱事件,從而去分發事件,處理事件的;typesBySubscriber在調用unregister(this)的時候,根據訂閱者找到EventType,又根據EventType找到訂閱事件,從而對訂閱者進行解綁。第二件事,若是是粘性事件的話,就立馬投遞、執行。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //獲取訂閱方法的參數類型 Class<?> eventType = subscriberMethod.eventType; //根據訂閱者和訂閱方法構造一個訂閱事件 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //獲取當前訂閱事件中Subscription的List集合 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //該事件對應的Subscription的List集合不存在,則從新建立並保存在subscriptionsByEventType中 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //訂閱者已經註冊則拋出EventBusException if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //遍歷訂閱事件,找到比subscriptions中訂閱事件小的位置,而後插進去 int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //經過訂閱者獲取該訂閱者所訂閱事件的集合 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //將當前的訂閱事件添加到subscribedEvents中 subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { //粘性事件的處理 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
3.3.3 流程圖
4.EventBus事件分發解析
4.1 從post方法入手
首先從PostingThreadState對象中取出事件隊列,而後再將當前的事件插入到事件隊列當中。最後將隊列中的事件依次交由postSingleEvent方法進行處理,並移除該事件。
/** Posts the given event to the event bus. */ public void post(Object event) { //獲取當前線程的postingState PostingThreadState postingState = currentPostingThreadState.get(); //取得當前線程的事件隊列 List<Object> eventQueue = postingState.eventQueue; //將該事件添加到當前的事件隊列中等待分發 eventQueue.add(event); if (!postingState.isPosting) { //判斷是不是在主線程post postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { //分發事件 postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
4.2 什麼是PostingThreadState?
PostingThreadState中包含了當前線程的事件隊列,就是當前線程全部分發的事件都保存在eventQueue事件隊列中以及訂閱者訂閱事件等信息,有了這些信息咱們就能夠從事件隊列中取出事件分發給對應的訂閱者
final static class PostingThreadState { final List<Object> eventQueue = new ArrayList<Object>();//當前線程的事件隊列 boolean isPosting;//是否有事件正在分發 boolean isMainThread;//post的線程是不是主線程 Subscription subscription;//訂閱者 Object event;//訂閱事件 boolean canceled;//是否取消 }
4.3 PostingThreadState怎麼得到?
ThreadLocal 是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據,而這段數據是不會與其餘線程共享的。
能夠看出currentPostingThreadState的實現是一個包含了PostingThreadState的ThreadLocal對象,這樣能夠保證取到的都是本身線程對應的數據。
咱們有了PostingThreadState獲取到了當前線程的事件隊列,接下來就是事件分發,咱們來看postSingleEvent(eventQueue.remove(0), postingState);
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } };
4.4 來看看postSingleEvent方法裏作了什麼
eventInheritance表示是否向上查找事件的父類,它的默認值爲true,能夠經過在EventBusBuilder中來進行配置。當eventInheritance爲true時,則經過lookupAllEventTypes找到全部的父類事件並存在List中,而後經過postSingleEventForEventType方法對事件逐一處理,接下來看看postSingleEventForEventType方法
事件分發 private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { //獲得事件類型 Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; //是否觸發訂閱了該事件(eventClass)的父類,以及接口的類的響應方法. if (eventInheritance) { List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }
4.5 接下來看看postSingleEventForEventType方法
同步取出該事件對應的Subscription集合並遍歷該集合將事件event和對應Subscription傳遞給postingState並調用postToSubscription方法對事件進行處理,接下來看看postToSubscription方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //根據事件類型獲取全部的訂閱者 subscriptions = subscriptionsByEventType.get(eventClass); } //向每一個訂閱者分發事件 if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //對事件進行處理 postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }
4.6 接下來看看postToSubscription方法
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING://默認的 ThreadMode,表示在執行 Post 操做的線程直接調用訂閱者的事件響應方法, //不論該線程是否爲主線程(UI 線程)。 invokeSubscriber(subscription, event); break; case MAIN://在主線程中執行響應方法。 if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case BACKGROUND://在後臺線程中執行響應方法。 if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC://不論發佈線程是否爲主線程,都使用一個空閒線程來處理。 asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
4.7 整個流程圖
4.8 總結一下整個事件分發的過程
首先獲取當前線程的PostingThreadState對象從而獲取到當前線程的事件隊列
經過事件類型獲取到全部訂閱者集合
經過反射執行訂閱者中的訂閱方法
5.EventBus取消註冊解析
5.1 unregister(this)方法入手
/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { //獲取訂閱者的全部訂閱的事件類型 List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { //從事件類型的訂閱者集合中移除訂閱者 unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
5.2 再來看看unsubscribeByEventType(subscriber, eventType)
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { //獲取事件類型的全部訂閱者 List<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //遍歷訂閱者集合,將解除的訂閱者移除 if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } }
5.3 取消註冊流程圖
5.4 總結一下取消註冊的過程
一、首先獲取訂閱者的全部訂閱事件
二、遍歷訂閱事件
三、根據訂閱事件獲取全部的訂閱了該事件的訂閱者集合
四、將該訂閱者移除
五、將步驟1中的集合中的訂閱者移除
6.總結一下EventBus的工做原理
6.1 訂閱邏輯
一、首先用register()方法註冊一個訂閱者
二、獲取該訂閱者的全部訂閱的方法
三、根據該訂閱者的全部訂閱的事件類型,將訂閱者存入到每一個以 事件類型爲key 以全部訂閱者爲values的map集合中
四、而後將訂閱事件添加到以訂閱者爲key 以訂閱者全部訂閱事件爲values的map集合中
五、若是是訂閱了粘滯事件的訂閱者,從粘滯事件緩存區獲取以前發送過的粘滯事件,響應這些粘滯事件。
6.2 事件發送邏輯
一、首先獲取當前線程的事件隊列
二、將要發送的事件添加到事件隊列中
三、根據發送事件類型獲取全部的訂閱者
四、根據響應方法的執行模式,在相應線程經過反射執行訂閱者的訂閱方法
6.3 取消邏輯
一、首先經過unregister方法拿到要取消的訂閱者
二、獲得該訂閱者的全部訂閱事件類型
三、遍歷事件類型,根據每一個事件類型獲取到全部的訂閱者集合,並從集合中刪除該訂閱者
四、將訂閱者從步驟2的集合中移除
6.4 利與弊
EventBus好處比較明顯,它可以解耦和,將業務和視圖分離,代碼實現比較容易。並且3.0後,咱們能夠經過apt預編譯找到訂閱者,避免了運行期間的反射處理解析,大大提升了效率。固然EventBus也會帶來一些隱患和弊端,若是濫用的話會致使邏輯的分散並形成維護起來的困難。另外大量採用EventBus代碼的可讀性也會變差。
7.其餘介紹
7.1 參考文檔
EventBus源碼,能夠看:https://github.com/greenrobot/EventBus
EventBus源碼解析:https://www.cnblogs.com/all88/archive/2016/03/30/5338412.html
EventBus用法解析:http://blog.csdn.net/itachi85/article/details/52205464
7.2 其餘
脈脈:yc930211
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com