EventBus源碼研讀(中)

Subscribe流程

我們繼續來看EventBus類,分析完了包含的屬性,接下來我們看入口方法register()

通過查看源碼我們發現,所有的register()方法,最後都會直接或者間接的調用register()方法

 
/**
 * @param subscriber 訂閱者對象
 * @param sticky     是否粘滯
 * @param 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);
    }
}

SubscriberMethod類

出現了一個SubscriberMethod類,看看它是幹嘛的:

看字面意思是訂閱者方法,看看類中的內容,除了複寫的equals()和hashCode()就只有這些了。

 
final Method method; //方法名
final ThreadMode threadMode; //工作在哪個線程
final Class<?> eventType; //參數類型
/** Used for efficient comparison */
String methodString;

private synchronized void checkMethodString() {
    if (methodString == null) {
        // Method.toString has more overhead, just take relevant parts of the method
        StringBuilder builder = new StringBuilder(64);
        builder.append(method.getDeclaringClass().getName());
        builder.append('#').append(method.getName());
        builder.append('(').append(eventType.getName());
        methodString = builder.toString();
    }
}

ThreadMode是一個枚舉類,是不是應該換成 int 更好呢。 checkMethodString()方法就是爲了設置變量 methodString 的值,這裏new了一個StringBuilder,然後又調用了toString()返回,是不是應該改成直接new String(format...)更好呢? 
OK,不管那些細節,看到這裏就知道,其實這個類也就是一個封裝了的方法名而已。

回到EventBus#register()咱們繼續. 噢,又遇到了SubscriberMethodFinder這又是啥,繼續去看。

SubscriberMethodFinder類

從字面理解,就是訂閱者方法發現者。
回想一下,我們之前用 EventBus 的時候,需要在註冊方法傳的那個 this 對象裏面寫一個 onEvent() 方法。沒錯,SubscriberMethodFinder類就是查看傳進去的那個 this 對象裏面有沒有onEvent()方法的。怎麼做到的?當然是反射。而且這個類用了大量的反射去查找類中方法名。

先看他的變量聲明

 
private static final String ON_EVENT_METHOD_NAME = "onEvent";

/**
 * 在較新的類文件,編譯器可能會添加方法。那些被稱爲BRIDGE或SYNTHETIC方法。
 * EventBus必須忽略兩者。有修飾符沒有公開,但在Java類文件中有格式定義
 */
private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;
//需要忽略的修飾符
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE |
        SYNTHETIC;

//key:類名,value:該類中需要相應的方法集合
private static final Map<String, List<SubscriberMethod>> methodCache = new HashMap<String,
        List<SubscriberMethod>>();

//跳過校驗方法的類(即通過構造函數傳入的集合)
private final Map<Class<?>, Class<?>> skipMethodVerificationForClasses;

有一句註釋

In newer class files, compilers may add methods. Those are called bridge or synthetic methods. EventBus must ignore both. There modifiers are not public but defined in the Java class file format: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1

翻譯過來大概就是說java編譯器在編譯的時候,會額外添加一些修飾符,然後這些修飾符爲了效率應該是被忽略的。

還有一個skipMethodVerificationForClasses,看到註釋是需要跳過被校驗方法的類,校驗方法是什麼?看看他是幹什麼的。findSubscriberMethods()方法有點長,咱們抽一點看。 跳過上面的那些臨時變量,從while循環裏開始看:

 
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    String methodName = method.getName();
    if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
        int modifiers = method.getModifiers();//方法的修飾符
        //如果是public,且 不是之前定義要忽略的類型
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //。。。先不看
        }
    }
}
clazz = clazz.getSuperclass();

首先是反射獲取到 clazz 的全部方法 methods。
通過對全部的方法遍歷,爲了效率首先做一次篩選,只關注我們的以 “onEvent” 開頭的方法。(現在知道之前在基礎用法中我說:其實命名不一定必須是onEvent()的原因了吧,因爲只要是onEvent開頭的就可以了。) 
忽略private類型的,最後如果是公有,並且不是 java編譯器 生成的方法名,那麼就是我們要的了。

再來看拿到要的方法後是怎麼處理的

 
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)) {
        // 方法名,工作在哪個線程,事件類型
        subscriberMethods.add(new SubscriberMethod(method, threadMode,
                eventType));
    }
}

還是反射,拿到這個方法的全部參數集合,如果是隻有一個參數,再去根據不同的方法名賦予不同的線程模式(其實也就是最後響應的方法是工作在哪個線程)。
這裏我們看到,其實EventBus不僅僅支持onEvent()的回調,它還支持onEventMainThread()onEventBackgroundThread()onEventAsync()這三個方法的回調。
一直到最後,我們看到這個方法把所有的方法名集合作爲value,類名作爲key存入了 methodCache 這個全局靜態變量中。意味着,整個庫在運行期間所有遍歷的方法都會存在這個 map 中,而不必每次都去做耗時的反射取方法了。

 
synchronized (methodCache) {
    methodCache.put(key, subscriberMethods);
}
return subscriberMethods;

看了這麼久,我們再回到 EventBus#register() 方法。這回可以看懂了,就是拿到指定類名的全部訂閱方法(以 onEvent 開頭的方法),並對每一個方法調用subscribe()。那麼再看subscribe()方法。

事件的處理與發送subscribe()

subscribe()方法接受四個參數,分別爲:訂閱者封裝的對象、響應方法名封裝的對象、是否爲粘滯事件(可理解爲廣播)、這條事件的優先級。

 
//根據傳入的響應方法名獲取到響應事件(參數類型)
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
//通過響應事件作爲key,並取得這個事件類型將會響應的全部訂閱者
//沒個訂閱者至少會訂閱一個事件,多個訂閱者可能訂閱同一個事件(多對多)
//key:訂閱的事件,value:訂閱這個事件的所有訂閱者集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);

//根據優先級插入到訂閱者集合中
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);
}
//key:訂閱者對象,value:這個訂閱者訂閱的事件集合
subscribedEvents.add(eventType);

跳過一些初始化的局部變量(邏輯看註釋就夠了)
如果傳入的事件是有優先級之分的,則會根據優先級,將事件插入所有訂閱了事件eventType的類的集合subscriptions中去。看邏輯我們發現,這裏並沒有對優先級的大小做限制,默認的優先級是0,priority越大,優先級越高。
每個訂閱者是可以有多個重載的onEvent()方法的,所以這裏多做了一步,將所有訂閱者的響應方法保存到subscribedEvents中。
至此,我們就知道了 EventBus 中那幾個map的全部含義。同時也回答了上一篇中問的爲什麼如果EventBus.defaultInstance不爲null以後程序要拋出異常,就是因爲這幾個 map 不同了。 map 變了以後,訂閱的事件就全部變爲另一個 EventBus 對象的了,就沒辦法響應之前那個 EventBus 對象的訂閱方法了。

最後又是一個感嘆:子事件也可以讓響應父事件的 onEvent() 。這個有點繞,舉個例子,訂閱者的onEvent(CharSequence),如果傳一個String類型的值進去,默認情況下是不會響應的,但如果我們在構建的時候設置了 eventInheritance 爲 true ,那麼它就會響應了。

 
if(sticky)
if (eventInheritance) {
    Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
    for (Map.Entry<Class<?>, Object> entry : entries) {
        Class<?> candidateEventType = entry.getKey();
        //如果eventtype是candidateEventType同一個類或是其子類
        if (eventType.isAssignableFrom(candidateEventType)) {
            Object stickyEvent = entry.getValue();
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
} else {
    Object stickyEvent = stickyEvents.get(eventType);
    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}

最後是調用checkPostStickyEventToSubscription()做一次安全判斷,就調用postToSubscription()發送事件了。
這裏就關聯到了我們之前講的Poster類的作用了。
回答之前的問題:Poster只負責粘滯事件的代碼。這裏可以回答一部分:如果不是 sticky 事件都直接不執行了,還怎麼響應。

 
private void postToSubscription(...) {
    switch (threadMode) {
        case PostThread:
            //直接調用響應方法
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            //如果是主線程則直接調用響應事件,否則使用handle去在主線程響應事件
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
            //。。。
    }
}

最後,還記得我們之前沒有講的那個invokeSubscriber(subscription, event);方法嗎? 之前我們不知道subscriberMethod是什麼,現在我們能看懂了,就是通過反射調用訂閱者類subscriber的訂閱方法onEventXXX(),並將event作爲參數傳遞進去

 
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

Register與Poster工作圖

原理圖

開源實驗室:圖6

流程圖

完整的註冊流程
開源實驗室:圖7

至此,整個EventBus從註冊訂閱到事件的處理到響應的過程我們都分析完了,最後就只剩下發送流程和取消註冊了。

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