Android 源碼解析:EventBus

EventBus源碼解析

EventBus 是一個 Android 事件發佈/訂閱框架,通過解耦發佈者和訂閱者簡化 Android 事件傳遞,事件傳遞既可用於 Android 四大組件間通訊,也可以用戶異步線程和主線程間通訊等等。傳統的事件傳遞方式包括:Handler、BroadCastReceiver、Interface 回調,相比之下 EventBus 的優點是代碼簡潔,使用簡單,並將事件發佈和訂閱充分解耦。

1 基本術語

事件(Event):又可稱爲消息,事件。事件類型(EventType)指事件所屬的 Class。

訂閱者(Subscriber):訂閱某種事件類型的對象。

發佈者(Publisher):發佈某事件的對象,通過 post 接口發佈事件。

2 工作流程



EventBus 負責存儲訂閱者、事件相關信息,訂閱者和發佈者都只和 EventBus 關聯。訂閱者首先調用 EventBus 的 register 接口訂閱某種類型的事件,當發佈者通過 post 接口發佈該類型的事件時,EventBus 執行調用者的事件響應函數。


事件分爲一般事件和 Sticky 事件,相對於一般事件,Sticky 事件不同之處在於,當事件發佈後,再有訂閱者開始訂閱該類型事件,依然能收到該類型事件最近一個 Sticky 事件。
當有發佈者發佈這類事件後,EventBus 會執行訂閱者的 onEvent 函數,訂閱者通過 register 接口訂閱某個事件類型,unregister 接口退訂。訂閱者存在優先級,優先級高的訂閱者可以取消事件繼續向優先級低的訂閱者分發,默認所有訂閱者優先級都爲 0。

主要方法:

EventBus.getDefault().register(this);//訂閱事件
EventBus.getDefault().post(object);//發佈事件
EventBus.getDefault().unregister(this);//取消訂閱
onEventMainThread(){ //反射異步回調的方法
}

實現原理:

       首先我們在onCreate裏面調用EventBus.getDefault().register(this); 接着在該類中調用回調方法onEventMainThread()等。目的是讓EventBus掃描當前類,把所有onEvent開頭的方法記錄下來(使用Map,Key爲方法的參數類型,Value中包含我們的方法)。

然後在onCreate執行完成以後,我們的onEventMainThread就已經以鍵值對的方式被存儲到EventBus中了。

再然後,當一個其他任務在子線程執行完畢,調用EventBus.getDefault().post(new Item(xx)))時,EventBus會根據post中實參的類型,去Map中查找對於的方法,於是找到了我們的onEventMainThread,最終調用反射去執行我們的方法。

這樣整個流程就完成了,實現了沒有接口卻能發生回調。

3.源碼實現

類的關係圖:




EventBus.java
EventBus 類負責所有對外暴露的 API,其中的 register()、post()、unregister() 函數配合上自定義的 EventType 及事件響應函數即可完成核心功能。


EventBus 默認可通過靜態函數 getDefault 獲取單例,當然有需要也可以通過 EventBusBuilder 或 構造函數新建一個 EventBus,每個新建的 EventBus 發佈和訂閱事件都是相互隔離的,即一個 EventBus 對象中的發佈者發佈事件,另一個 EventBus 對象中的訂閱者不會收到該訂閱。

源碼如下:

/** 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;
}
使用了雙重判斷的方式,防止併發的問題,還能極大的提高效率。

/**
 * Creates a new EventBus instance; each instance is a separate scope in which events are delivered.To * use a central bus, consider {@link #getDefault()}.
 */
public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
    typesBySubscriber = new HashMap<Object, List<Class<?>>>();
    stickyEvents = new ConcurrentHashMap<Class<?>, Object>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    subscriberMethodFinder = new SubscriberMethodFinder(builder.skipMethodVerificationForClasses);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}


EventBus 中對外 API,register()和post()是最爲關鍵的方法。


(1) register 

分別表示訂閱事件和取消訂閱。register 最底層函數有三個參數,分別爲訂閱者對象、是否是 Sticky 事件、優先級。

源碼如下:

public void register(Object subscriber) {
    register(subscriber, false, 0);
}
public void register(Object subscriber, int priority) {
    register(subscriber, false, priority);
}
private synchronized void register(Object subscriber, boolean sticky, int priority) {
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}
本質上都是調用了三個參數的方法。PS:在此之前的版本 EventBus 還允許自定義事件響應函數名稱,這版本中此功能已經被去除。
在註冊方法中,調用內部類SubscriberMethodFinder的findSubscriberMethods方法,傳入了subscriber 的class,以及methodName,返回一個List<SubscriberMethod>。那麼不用說,肯定是去遍歷該類內部所有方法,然後根據methodName去匹配,匹配成功的封裝成SubscriberMethod,最後返回一個List。
下面看看源碼中findSubscriberMethods()方法的實現。
/**
 * 通過subscriberClass,去遍歷subscriber類內部的所有方法,然後根據methodName去匹配.
 * 匹配成功的封裝成SubscriberMethod,最後返回一個List<SubscriberMethod>
 *
 * @param subscriberClass
 * @return List<SubscriberMethod>
 */
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
  String key = subscriberClass.getName();
  List<SubscriberMethod> subscriberMethods;
  synchronized (methodCache) {
    subscriberMethods = methodCache.get(key);
  }
  if (subscriberMethods != null) {
    return subscriberMethods;
  }
  subscriberMethods = new ArrayList<SubscriberMethod>();
  Class<?> clazz = subscriberClass;
  HashSet<String> eventTypesFound = new HashSet<String>();
  StringBuilder methodKeyBuilder = new StringBuilder();
  while (clazz != null) {
    String name = clazz.getName();
    if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
      // Skip system classes, this just degrades performance
      break;
    }

    // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
      String methodName = method.getName();
      if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
          Class<?>[] parameterTypes = method.getParameterTypes();
          if (parameterTypes.length == 1) {
            String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
            ThreadMode threadMode;
            if (modifierString.length() == 0) {
              threadMode = ThreadMode.PostThread;
            } else if (modifierString.equals("MainThread")) {
              threadMode = ThreadMode.MainThread;
            } else if (modifierString.equals("BackgroundThread")) {
              threadMode = ThreadMode.BackgroundThread;
            } else if (modifierString.equals("Async")) {
              threadMode = ThreadMode.Async;
            } else {
              if (skipMethodVerificationForClasses.containsKey(clazz)) {
                continue;
              } else {
                throw new EventBusException("Illegal onEvent method, check for typos: " + method);
              }
            }
            Class<?> eventType = parameterTypes[0];
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(methodName);
            methodKeyBuilder.append('>').append(eventType.getName());
            String methodKey = methodKeyBuilder.toString();
            if (eventTypesFound.add(methodKey)) {
              // Only add if not already found in a sub class
              subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
            }
          }
        } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
          Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "." + methodName);
        }
      }
    }
    clazz = clazz.getSuperclass();
  }
  if (subscriberMethods.isEmpty()) {
    throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " + ON_EVENT_METHOD_NAME);
  } else {
    synchronized (methodCache) {
      methodCache.put(key, subscriberMethods);
    }
    return subscriberMethods;
  }
}
方法解析:
首先是要clazz.getMethods();去得到所有的方法,接着就開始遍歷每一個方法了,去匹配封裝了。分別判斷了是否以onEvent開頭,是否是public且非static和abstract方法,是否是一個參數。如果都符合要求,才進入封裝匹配根據方法的後綴,來確定threadMode,threadMode是個枚舉類型:就四種情況。最後,將method, threadMode, eventType傳入構造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,最終放回。
注意下面的 clazz = clazz.getSuperclass();可以看到,會掃描所有的父類,不僅僅是當前類。

接着看看for循環中的subscribe(subscriber, subscriberMethod, sticky, priority)方法的實現:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
    Class<?> eventType = subscriberMethod.eventType;
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<Subscription>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    // subscriberMethod.method.setAccessible(true);

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<Class<?>>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            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);
        }
    }
}
方法解析:

注意:subscriberMethod中保存了method, threadMode, eventType。

該方法首先根據subscriberMethod.eventType,去subscriptionsByEventType。去查找一個CopyOnWriteArrayList<Subscription> ,如果沒有則創建。順便把我們的傳入的參數封裝成了一個:Subscription(subscriber, subscriberMethod, priority);

這裏的subscriptionsByEventType是個Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 這個Map其實就是EventBus存儲方法的地方!

接下來,就是添加newSubscription;並且是按照優先級添加的。可以看到,優先級越高,會插到在當前List的前面。

然後,根據subscriber存儲它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;這裏主要用於isRegister的判斷。

最後,就是判斷sticky;如果爲true,從stickyEvents中根據eventType去查找有沒有stickyEvent,如果有則立即發佈去執行。stickyEvent其實就是我們post時的參數。

register()總結:

EventBus掃描了所有的方法,把匹配的方法最終保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我們方法參數的Class,Subscription中則保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;其中,包含了執行改方法所需的一切。



(2) post 方法


直接看EnvetBus中方法的實現源碼:

/** Posts the given event to the event bus. */
public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    if (!postingState.isPosting) {
        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;
        }
    }
}

方法解析:

在register時,把方法存在subscriptionsByEventType;那麼post肯定會去subscriptionsByEventType去取方法,然後調用。

其中,currentPostingThreadState是一個ThreadLocal類型的,裏面存儲了PostingThreadState;PostingThreadState包含了一個eventQueue和一些標誌位。

首先,把傳入的event,保存到了當前線程中的一個變量PostingThreadState的eventQueue中。然後判斷當前是否是UI線程。

最後,遍歷隊列中的所有的event,調用postSingleEvent(eventQueue.remove(0), postingState)方法。


接下來看看postSingleEvent(eventQueue.remove(0), postingState)方法的實現:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    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));
        }
    }
}

方法解析:

首先,根據event的Class,去得到一個List<Class<?>>;其實就是得到event當前對象的Class,以及父類和接口的Class類型;主要用於匹配包括父類。

接下來,遍歷所有的Class,到subscriptionsByEventType去查找subscriptions;當我們register裏面把方法保存到這個Map裏了;

然後,遍歷每個subscription,依次去調用postToSubscription(subscription, event, postingState.isMainThread);
這個方法就是去反射執行回調方法了,在register,if(sticky)時,也會去執行這個方法。

最後,看看postToSubscription(subscription, event, postingState.isMainThread)這個方法:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case PostThread:
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BackgroundThread:
            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);
    }
}
方法解析:

第一步根據threadMode去判斷應該在哪個線程去執行該方法。

case PostThread直接反射調用;也就是說在當前的線程直接調用該方法;

case MainThread:首先去判斷當前如果是UI線程,則直接調用;否則: mainThreadPoster.enqueue(subscription, event);把當前的方法加入到隊列,然後直接通過handler去發送一個消息,在handler的handleMessage中,去執行我們的方法。說白了就是通過Handler去發送消息,然後執行的。

 case BackgroundThread如果當前非UI線程,則直接調用;如果是UI線程,則將任務加入到後臺的一個隊列,最終由Eventbus中的一個線程池去調用executorService = Executors.newCachedThreadPool();

 case Async:將任務加入到後臺的一個隊列,最終由Eventbus中的一個線程池去調用;線程池與BackgroundThread用的是同一個。

 BackgroundThread和Async有什麼區別呢?

BackgroundThread中的任務,一個接着一個去調用,中間使用了一個布爾型變量handlerActive進行的控制。

Async則會動態控制併發。

總結:到此,Eventbus類源碼分析的就差不多了,Eventbus類是這個事件發佈庫的門面類,包含了所有重要的與外界打交道的api.

register會把當前類中匹配的方法,存入一個map,而post會根據實參去map查找進行反射調用。也可以說,就是在一個單例內部維持着一個map對象存儲了一堆的方法;post無非就是根據參數去查找方法,進行反射調用。


EventBus中其他的類的介紹:

SubscriberMethod.java

訂閱者事件響應函數信息,包括響應方法、線程 Mode、事件類型以及一個用來比較 SubscriberMethod 是否相等的特徵值 methodString 共四個變量,其中 methodString 爲 ${methodClassName}#${methodName}(${eventTypeClassName}。

Subscription.java

訂閱者信息,包括 subscriber 對象、事件響應方法 SubscriberMethod、優先級 priority。

HandlerPoster.jva

事件主線程處理,對應ThreadMode.MainThread。繼承自 Handler,enqueue 函數將事件放到隊列中,並利用 handler 發送 message,handleMessage 函數從隊列中取事件,invoke 事件響應函數處理。

 AsyncPoster.java

事件異步線程處理,對應ThreadMode.Async,繼承自 Runnable。enqueue 函數將事件放到隊列中,並調用線程池執行當前任務,在 run 函數從隊列中取事件,invoke 事件響應函數處理。

BackgroundPoster.java

事件 Background 處理,對應ThreadMode.BackgroundThread,繼承自 Runnable。enqueue 函數將事件放到隊列中,並調用線程池執行當前任務,在 run 函數從隊列中取事件,invoke 事件響應函數處理。與 AsyncPoster.java 不同的是,BackgroundPoster 中的任務只在同一個線程中依次執行,而不是併發執行。

 PendingPost.java

訂閱者和事件信息實體類,並含有同一隊列中指向下一個對象的指針。通過緩存存儲不用的對象,減少下次創建的性能消耗。

 PendingPostQueue.java

通過 head 和 tail 指針維護一個PendingPost隊列。HandlerPoster、AsyncPoster、BackgroundPoster 都包含一個此隊列實例,表示各自的訂閱者及事件信息隊列,在事件到來時進入隊列,處理時從隊列中取出一個元素進行處理。

SubscriberExceptionEvent.java

當調用事件處理函數異常時發送的 EventBus 內部自定義事件,通過 post 發送,訂閱者可自行訂閱這類事件進行處理。

NoSubscriberEvent.java

當沒有事件處理函數對事件處理時發送的 EventBus 內部自定義事件,通過 post 發送,訂閱者可自行訂閱這類事件進行處理。

 EventBusException.java

封裝於 RuntimeException 之上的 Exception,只是覆蓋構造函數,相當於一個標記,標記是屬於 EventBus 的 Exception。

ThreadMode.java

線程 Mode 枚舉類,表示事件響應函數執行線程信息,包括ThreadMode.PostThreadThreadMode.MainThreadThreadMode.BackgroundThreadThreadMode.Async四種。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章