1. 程式人生 > >EventBus原始碼解析(一)—訂閱過程

EventBus原始碼解析(一)—訂閱過程

1.EventBus原始碼解析(一)—訂閱過程
2.EventBus原始碼解析(二)—釋出事件和登出流程

前言

最近發現EventBus用起來是真的方便,本來對於EventBus我對於這個框架的原始碼的閱讀的優先順序是比較低的,因為這個框架不像OkHttp,Glide那樣層層巢狀,步步深入,基本上有一定基礎的人對於EventBus的原理都會有一定的理解——反射。但是最近突然發現僅僅是瞭解這些是不夠的,如果細想一下會發現,EventBus的實現還是有許多值得學習的地方的,我來說一下讓我疑惑的問題以至於想讓我去追究EventBus底層原始碼的問題吧。

1.EventBus在接受事件的時候如何區分多個相同的Activity?
2.EventBus事件的接收在父類和子類之間是什麼規則?
3.EventBus對於多型型別的事件是如何識別的?
4.EventBus只能用於Activity和Fragment之間嗎?
5.EventBus可以建立多個例項嗎?互相干擾嗎?

再沒有看EventBus原始碼之前,上面這些問題,我是沒法回答的。意識到看原始碼的必要性,我提高的EventBus原始碼閱讀的優先順序,本篇部落格主要從分析註冊流程。

原始碼分析

註冊流程

EventBus.getDefault().register(this);

EventBus的使用本篇部落格就不講解了,註冊的程式碼很簡單,一行遍完成,接下來詳細看看實現原始碼。

1.getDefault()

public static EventBus getDefault() {
        //常規的雙重鎖單例模式,特殊的是構造方法是公有的,也就是可以建立多個EventBus,互相不干擾
EventBus instance = defaultInstance; if (instance == null) { synchronized (EventBus.class) { instance = EventBus.defaultInstance; if (instance == null) { instance = EventBus.defaultInstance = new EventBus(); } } } return
instance; }

對於getDefault()不出意料果然是常規的雙重鎖的單例模式,但是這裡有一個地方和平常的單例模式不同,平常的單例模式構造方法往往是私有的,用於保證全域性唯一,但是這裡我們看一下EventBus的建構函式。

/**
     * 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的構造方法是公有的,也就是說我們自己可以自己new一個EventBus的例項,當然EventBus也提供了EventBusBuilder供我們構造使用。這裡特意把構造方法的註釋放了上來,大概翻譯一下吧。

建立一個EventBus例項,每一個例項的事件傳遞都在自己的例項範圍,如果想統一管理,可以使用getDefault()方法。
這裡通過註釋我們就可以解釋第五個問題了,當然只是從註釋層面,EventBus是可以建立多個例項的,並且多個例項的傳送的事件是互不干擾的

2.register(Object subscriber)

public void register(Object subscriber) {
        //獲得訂閱者對應的Class,MainActivity.class
        Class<?> subscriberClass = subscriber.getClass();
        //找到所有的訂閱的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

這裡開始看註冊的過程,可以看到往往我們註冊時傳入的是this,所以這裡首先呼叫getClass(),獲得的就是MainActivity.class(舉例),獲得完這個class物件後,會呼叫findSubscriberMethods方法來找到該Class中所有的訂閱方法。

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //Cache中存在則直接從Cache中獲取
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            //執行是通過反射拿到
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //編譯期獲得
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        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對應的是在宣告的時候就初始化完成了,是一個ConcurrentHashMap

private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

可以看到這裡的key是class物件,那麼我們可以發現,如果已經通過反射得到的訂閱方法的類,再次註冊的時候,不會再次通過反射獲取。
下面可以看到對於ignoreGeneratedIndex變數的判斷,這個變數預設是true,也就是預設的時候我們是通過執行時反射來回去訂閱的方法的,當然我們可以通過配置索引並設定ignoreGeneratedIndex為false,來實現編譯期獲得訂閱方法。本篇部落格就不追究後面這種方式,對於索引的方式,後面的部落格如果有時間會考慮分析原始碼。

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        //快取中獲取,預設大小為4都陣列,沒有就new
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //findState儲存註冊的訂閱者中的方法相關資訊
            findUsingReflectionInSingleClass(findState);
            //向上繼續遍歷
            findState.moveToSuperclass();
        }
        //返回方法集
        return getMethodsAndRelease(findState);
    }

這裡可以看到首先獲取FindState物件,使用了prepareFindState方法

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private FindState prepareFindState() {
        //有點簡單粗暴的意思。。。
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                FindState state = FIND_STATE_POOL[i];
                if (state != null) {
                    FIND_STATE_POOL[i] = null;
                    return state;
                }
            }
        }
        return new FindState();
    }

可以看到預設是有一個大小為4的陣列用於快取FindState物件的,由於FindState物件比較大,所以EventBus考慮使用快取進行復用,當然這個快取實現起來也比較簡單粗暴~
接下來是一個迴圈。

while (findState.clazz != null) {
            //findState儲存註冊的訂閱者中的方法相關資訊
            findUsingReflectionInSingleClass(findState);
            //向上繼續遍歷
            findState.moveToSuperclass();
        }

可以看到,此時我們的findState.clazz是MainActivity.class(舉例),我們這裡先看一下遍歷的規則,再來看遍歷裡幹了些什麼,首先這裡的判斷依據是根據findState.clazz!=null.每次執行完findUsingReflectionInSingleClass(findState);方法都會執行findState.moveToSuperclass方法。

void moveToSuperclass() {
            //如果配置忽略父類方法則不檢查父類方法
            if (skipSuperClasses) {
                clazz = null;
            } else {
                clazz = clazz.getSuperclass();
                String clazzName = clazz.getName();
                /** Skip system classes, this just degrades performance. */
                if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
                    clazz = null;
                }
            }
        }

這裡可以看到,方法的作用就是向上遍歷父Class,可以通過配置不向上遍歷父Class,但是預設是會先檢查父Class,而且這裡需要注意的一個點,向上遍歷的終點是檢查到Class是以“java.””javax.”“android.”開頭的,也就是說對於系統原始碼內的class是不會檢查的,這時class變成null,整個迴圈也就結束了。所以這裡又可以解釋我們的一個疑惑,預設EventBus是會檢查父類中的訂閱方法的。


private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            //通過註釋其實也可以看到EventBus團隊對於反射效能都考慮,所以用getDeclaredMethods而不是getMethods
            //獲取都不包括父類和介面都方法,包括私有都,
            //疑問點1
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            //方法必須是Public,不能是static,abstract,生成索引時會出問題
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                //獲取方法都引數
                Class<?>[] parameterTypes = method.getParameterTypes();
                //引數個數=1
                if (parameterTypes.length == 1) {
                    //方法是用@Subscribe註解
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        //獲取第一個引數都Class,也就是事件類都class
                        Class<?> eventType = parameterTypes[0];
                        // 檢查eventType決定是否訂閱,通常訂閱者不能有多個eventType相同的訂閱方法
                        //疑問點2
                        if (findState.checkAdd(method, eventType)) {
                            //獲得Thread型別
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            //ArrayList加入一個SubscriberMethod物件
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

接下來看到這個獲得訂閱的方法。這裡註釋寫的都比較全了這裡有幾個疑問點是需要我們瞭解的:

疑問點1

可以看到這裡使用了getDeclaredMethods而沒有使用getMethods,這裡通過註釋我們可以看到,這裡EventBus團隊出於對於反射效能的考慮,使用getDeclaredMethods代替getMethods方法,這裡可能有疑問了,這樣使用為什麼能提高效能?
首先我們來大概解釋一下兩個方法的區別吧:

public Method[] getMethods()返回某個類的所有公用(public)方法包括其繼承類的公用方法,當然也包括它所實現介面的方法。
public Method[] getDeclaredMethods()物件表示的類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方
法。當然也包括它所實現介面的方法。

起初我也在疑惑為什麼這樣能夠提升效能,直達看到了一篇部落格,這篇部落格也是對EventBus的原始碼的講解,中間提到了的關於這裡效能提升的講解,這裡也是EventBus在完善一個issue的解決方案。這裡大概解釋一下:
可以看到這兩個方法比較顯著的區別是,getDeclaredMethods只會獲取當前類的所有方法,而getMethods會獲取所有公用方法,包括繼承的。而前面也提到,EventBus在查詢完這裡後,會呼叫findState.moveToSuperclass(),向上繼續查詢父類的方法,所以這裡使用getMethods會重複查詢父類的方法。

疑問點2

findState.checkAdd(method, eventType)這層檢查是什麼作用的?
這裡提出兩個特殊場景(一般應該也不會有人這樣寫吧):

1.一個類中存在兩個方法名不同,但是是同一個Event型別的訂閱方法,EventBus會怎樣處理?
2.子類和父類存在相同方法名和Event型別的訂閱方法,EventBus會怎樣處理?

這個方法的作用就是處理上面這兩種情況的,所以還是需要我們追究一下這裡的原始碼實現:

boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            //HashMap,一般一個Event只有一個方法
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                    //不存在該引數型別的方法,直接新增到訂閱者
                return true;
            } else {
                if (existing instanceof Method) {
                    //存在相同的引數型別,代表引數型別相同,但可能方法名不同,或者方法名也相同(父類中的被重寫的方法)
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

這裡可以看到anyMethodByEventType是一個HashMap,如果當前Event的型別是原來沒有過的,直接返回true,加入到anyMethodByEventType中。而當這個引數型別已經被加入過了,存在兩種情況,也就是剛才已經提到的兩種情況,代表引數型別相同,但可能方法名不同,或者方法名也相同(父類中的被重寫的方法),這時就要進行checkAddWithMethodSignature方法的檢查。

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(method.getName());
            methodKeyBuilder.append('>').append(eventType.getName());

            String methodKey = methodKeyBuilder.toString();
            Class<?> methodClass = method.getDeclaringClass();
            Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
            if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
            //methodClassOld = null,說明時方法名不同的情況,直接返回true,會加入訂閱。
                //返回true
                // Only add if not already found in a sub class
                return true;
            } else {
            //methodClass一定是methodClassOld的父類,因為是向上遍歷的,所以父類的方法不會被加入訂閱
                // Revert the put, old class is further down the class hierarchy
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

可以看到這裡同樣是一個HashMap型別的subscriberClassByMethodKey,而key值對應的就是以“方法名>EventType名”。

Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);

第一種情況,一個類中存在兩個方法名不同,但是是同一個Event型別的訂閱方法,這時由於方法名不同,所有key值不同,所有每次methodClassOld = null,所以接下來的判斷條件裡,會直接返回true。加入這個訂閱方法。

第二種情況,子類和父類存在相同方法名和Event型別的訂閱方法(也就是重寫),這時由於方法名相同,Event型別也相同,所以每次methodClassOld != null,這時就要看第二個判斷條件,methodClassOld.isAssignableFrom(methodClass),這裡我們就要明白isAssignableFrom函式的作用。

class1.isAssignableFrom(class2) class2是不是class1的子類或者子介面

這裡我們就會發現在這種情況下,methodClassOld一定是methodClass的子類。因為從前面的分析我們知道,EventBus是從子類向上遍歷的過程,所以先加入的肯定是子類的方法,後加入的肯定是父類的方法,所以methodClassOld.isAssignableFrom(methodClass),這個判斷肯定是false,所以最終我們在這種情況下會返回false。最終也就是說明子類重寫的方法會加入訂閱,但是父類的被重寫的方法不會被加入訂閱。

向上回溯到register方法

public void register(Object subscriber) {
        //獲得訂閱者對應的Class,MainActivity.class
        Class<?> subscriberClass = subscriber.getClass();
        //找到所有的訂閱的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

到這裡我們已經分析完subscriberMethodFinder.findSubscriberMethods(subscriberClass)中反射到方式獲取所有的訂閱方法了。可以看到,在獲得list後,會進行一次遍歷。執行subscribe方法。

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        //每次都會new了一個Subscription物件,subsciber代表我們的訂閱者MainActivity.class, subscriberMethod代表其中的一個訂閱的方法。
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //判斷是否有以Event.class為key
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        //為null說明不存在該引數型別的方法沒有被訂閱過
        if (subscriptions == null) {
            //new 一個
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //subscriptions不為null的情況是指存在多個訂閱這個Event的類。
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        //如果長度不為0代表該事件有大於一個訂閱者,或一個訂閱中中有多個訂閱方法訂閱這個Event,相同引數名的方法
        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.add(eventType);

        //粘性事件相關,後面部落格會分析
        //下面是粘性事件的程式碼
    }

這裡就要提到EventBus中我認為最為重要的兩個Map中的第一個Map——subscriptionsByEventType,到後來看完EventBus的原始碼後你會發現理解這兩個Map基本上就理解了EventBus的基本原理。

首先來看第一個Map的資料結構。

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
    }

可以看到這裡map是以class為key,CopyOnWriteArrayList為value的HashMap,這裡要強調一下是HashMap,後面會解釋為什麼要強調。(HashMap的底層原理?)
接下來我們根據上面的原始碼來理解這個Map的作用。

CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);

首先嚐試從subscriptionsByEventType中嘗試是否已經存在過這個EventType.

if (subscriptions == null) {
            //new 一個
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //subscriptions不為null的情況是指方法名不同,但是引數型別相同。也就是剛才提到的兩種情況中的第一種
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

當不存在,處理就比較簡單,new一個CopyOnWriteArrayList,並且在subscriptionsByEventType中加入一條資料。
當存在這個EventType,則這時就要判斷一下subscriptions.contains(newSubscription)是否已經註冊過這個型別。這裡我們要詳細理解一下。
如何判斷一個類已經註冊過了。

//每次都會new了一個Subscription物件,subsciber代表我們的訂閱者MainActivity.class, subscriberMethod代表其中的一個訂閱的方法。
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);

而我們知道subscriptions是一個CopyOnWriteArrayList。這時我們就要看一下CopyOnWriteArrayList的原始碼了,contain()最後會走到indexOf()方法。

public boolean contains(Object var1) {
        Object[] var2 = this.getArray();
        return indexOf(var1, var2, 0, var2.length) >= 0;
    }
//var0 是引數
//var1 是CopyOnWriteArrayList內部的陣列
//var2 是0
//var3 是陣列的長度
private static int indexOf(Object var0, Object[] var1, int var2, int var3) {
        int var4;
        if(var0 == null) {
            for(var4 = var2; var4 < var3; ++var4) {
                if(var1[var4] == null) {
                    return var4;
                }
            }
        } else {
            for(var4 = var2; var4 < var3; ++var4) {
            //是通過equal判斷
                if(var0.equals(var1[var4])) {
                    return var4;
                }
            }
        }

        return -1;
    }

這裡
可以看到,如果判斷這個是否已經訂閱過了,最後會通過equal方法判斷,哪理所當然Subscription肯定重寫了equal方法。

@Override
    public boolean equals(Object other) {
        if (other instanceof Subscription) {
            Subscription otherSubscription = (Subscription) other;
            //==比較的是記憶體地址,所以如果兩個類地址不同,也不相同(棧記憶體在兩個MainActivity),
            //是否存在相同的訂閱類名,方法名,引數型別名(同一個Activity中存在兩個不同的方法訂閱這個Event)。
            return subscriber == otherSubscription.subscriber
                    && subscriberMethod.equals(otherSubscription.subscriberMethod);
        } else {
            return false;
        }
    }

對於兩個
最終這裡還會比較SubscriberMethod重寫的equal方法,最後會比較是否存在相同的訂閱類名,方法名,引數型別名。這裡其實就是我們剛才討論的兩種特殊情況中的第一種,當存在兩個EventType相同,但是方法名不同的情況。如果一個類中存在多個方法名相同,引數型別相同的方法,則會拋異常。

int size = subscriptions.size();
//如果長度不為0代表有大於1個的不同訂閱者,或者方法名,相同引數名的方法
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                //按優先順序加入
                subscriptions.add(i, newSubscription);
                break;
            }
        }

當沒有拋異常,則會按照優先順序進行加入,若沒有定義優先順序,則末尾加入。到這對於第一個Map我們其實已經有了一個比較深刻的理解

第一個Map的作用:儲存對於一個Event所有的訂閱者和訂閱方法,
這裡就放上一張圖吧,個人感覺非常有用。

subscriptionsByEventType

//維持當前訂閱者訂閱的所有Event
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

緊接著,我們會看到EventBus中另一個重要的Map-typesBySubscriber,和剛才一樣,看一下資料結構。

private final Map<Object, List<Class<?>>> typesBySubscriber;

typesBySubscriber = new HashMap<>();

不出意外,還是HashMap,這裡的邏輯就比較好理解了,key對應的是訂閱者型別,Value對應的是一個List

疑惑

這裡其實就要解釋我們的一個疑惑了,當棧記憶體在多個相同的MainActivity時,Eventbus如何訂閱的?

這裡就和我們兩個Map有關了,首先第一個Map中對於是否重複訂閱使用的contain函式進行判斷,最後包含==的判斷,也就是記憶體地址的判斷,不同的>Activity物件,記憶體地址肯定不同。

其次第二個Map,是一個HashMap,key就是我們的MainActivity,是一個Object型別,那麼多個MainActivity時,加入第二個HashMap,為什麼能區分哪?因為hashCode不同,根據HashMap的原理,不同的MainActivity的hashcode不同,當然可以都加入了。

後面的程式碼就是粘性事件相關的程式碼邏輯了,後面的部落格我們會分析。

總結

這裡我們來總結一下注冊的流程:

1.獲取訂閱類裡的所有的訂閱的方法,其中訂閱類父類的方法也會被記錄,重寫的方法不會被記錄,方法名不同,引數型別相同的方法會被記錄
2.填充兩個Map,第一個Map記錄訂閱了Event的所有訂閱者和其方法,第二個Map記錄了所有的訂閱者和其訂閱的所有Event。