1. 程式人生 > >EventBus3.0原始碼解析

EventBus3.0原始碼解析

對於EventBus相信做Android程式設計師們並不陌生,他有很多優點:

  • 它可以簡單Android元件之間的通訊
  • 它可以避免了Android四大元件複雜的生命週期處理
  • 它可以讓你的程式碼更為簡潔。

當我們要呼叫EventBus的功能時,比如註冊或者傳送事件,總會呼叫EventBus.getDefault()來獲取EventBus例項:

    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if
(defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }

很明顯是一個單例模式,那麼看看EventBus構造方法:

    public EventBus() {
        this(DEFAULT_BUILDER);
    }

這裡傳入一個EventBusBuilder例項,對EventBus變數進行初始化。
得到到EventBus後,便可以呼叫register方法將訂閱者註冊到EventBus中,看看register方法:

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

看到第三行,根據名字我們也就知道程式碼是去查詢訂閱者的方法,我們進入到裡面看看:

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        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;
        }
    }

程式碼的第二行我們看到先從方法快取裡面查詢是否有要查詢的方法,如果返回的不為空則直接返回,如果為空則執行第十行的 subscriberMethods = findUsingInfo(subscriberClass);(這裡因為ignoreGeneratedIndex預設為False)我們進入到方法裡面看個究竟:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != 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);
    }

這裡通過findState.subscriberInfo = getSubscriberInfo(findState);來得到訂閱者的資訊,然後到indUsingReflectionInSingleClass(findState);

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            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();
            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<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            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");
            }
        }
    }

這個程式碼很簡單,有註釋應該沒有問題,不過到了最裡面有一個findState.checkAdd(method, eventType);這個根據名字可以理解為檢查新增,這個方法返回一個布林型的,當然我們不能單獨的停留在這個猜想上面,我們要進入去探個究竟:

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.
            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以方法引數為key,儲存方法例項,正常的時候我們不會在父子類中都用相同的方法變數型別作為方法的引數註冊訂閱方法,這時候existing為null所以直接返回True,但是如果父子類中都有註冊訂閱方法,並且方法引數型別相同的話,就會執行到這裡,下面舉個例子來解釋這裡的執行過程:假如有一個頁面存在繼承關係,並且實現了public void onMessageEvent(Object object),這樣在子類執行到這裡的時候將呼叫anyMethodByEventType.put(eventType, method);將方法變數型別和方法儲存到anyMethodByEventType裡面,然後回到findUsingReflectionInSingleClass(FindState findState)方法裡面儲存到findState中,終於出來了回到findUsingInfo(Class

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)) {
                // Only add if not already found in a sub class
                return true;
            } else {
                // Revert the put, old class is further down the class hierarchy
                //保證儲存的是最底層的class
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

這時在第一遍呼叫的時候methodClassOld為空所以會返回true,然後回到上層方法中執行anyMethodByEventType.put(eventType, this);繼而再執行返回checkAddWithMethodSignature(method, eventType);不過這裡請注意,第一次呼叫的時候傳遞的第一個引數型別是子類的Method,而這次卻是父類了,如果這裡沒有注意到的話,理解就會出現各種問題。7行的if中methodClassOld不為空所以判斷後面的條件,當然這裡methodClassOld是methodClassOld的子類所以也是返回空,所以執行Else裡面的程式碼,所以父類的方法不會被加入到訂閱者的註冊方法中。
現在回到register(Object subscriber)方法中,在同步程式碼塊中呼叫了subscribe(subscriber, subscriberMethod);方法程式碼如下:

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

        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.add(eventType);

        if (subscriberMethod.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);
            }
        }
    }

這裡不用管那麼多的程式碼,第三行以傳進來的兩個引數構造了一個Subscription物件然後在後面for迴圈中加入到了儲存所有Subscription的集合中,到此事件的註冊已經告一段落。下面我們來了解事件的傳送。
我們知道事件傳送程式碼是:EventBus.getDefault().post(1);
那麼我們進去看看傳送的程式碼是怎麼實現的:

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;
            }
        }
    }

第二行PostingThreadState儲存著事件佇列和執行緒狀態資訊,得到事件佇列後將傳送的加入到裡面然後進入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));
            }
        }
    }

eventInheritance預設是true所以執行內部程式碼lookupAllEventTypes(eventClass);則通過lookupAllEventTypes找到所有的父類事件並存在List中,然後通過postSingleEventForEventType方法對事件逐一處理,接下來看看postSingleEventForEventType方法:

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;
    }

在這裡我們看到通過傳送訊息的型別得到了所有的觀察者,然後遍歷所有的觀察者,挨個呼叫postToSubscription方法進行訊息傳送postToSubscription方法內部的實現:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                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);
        }
    }

這裡根據方法的執行緒模式,分別處理,我們可以看到其中一種是直接呼叫,另一種是加入請求對列不過兩種方式最後呼叫的都是invokeSubscriber(subscription, event);裡面實現方式如下:

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

很明顯是用反射去呼叫觀察者註冊的方法到這裡EventHub原始碼大概的流程已經走完。只是為了做一個筆記不喜勿噴。

相關推薦

EventBus3.0原始碼解析-02

EventBus3.0原始碼解析-01這篇文章簡單的介紹了EventBus的使用流程,我們知道EventBus有三個主要入口方法,分別為 EventBus.getDefault().register(Object); EventBus.getDefault().

EventBus3.0原始碼解析-01

最近在學習EventBus原始碼,本著學習完之後要及時總結,方便以後回顧的心態,這裡做一個系列來分析EventBus的原始碼。本系列討論的都是最新的EventBus3.0的原始碼。EventBus GitHub地址 EventBus在gradle工程中引入的方式如下: imple

EventBus3.0原始碼解析

對於EventBus相信做Android程式設計師們並不陌生,他有很多優點: 它可以簡單Android元件之間的通訊 它可以避免了Android四大元件複雜的生命週期處理 它可以讓你的程式碼更為簡潔。 當我們要呼叫EventBus的功能時,比如註冊

Redis5.0原始碼解析(七)----------字串物件

基於Redis5.0 字串物件 字串物件的編碼可以是 int 、 raw 或者 embstr 如果一個字串物件儲存的是整數值, 並且這個整數值可以用 long 型別來表示, 那麼字串物件會將整數值儲存在字串物件結構的 ptr 屬性裡面(將 void* 轉

Redis5.0原始碼解析(六)----------Redis物件

基於Redis5.0 之前介紹了 Redis 用到的所有主要資料結構, 比如簡單動態字串(SDS)、雙端連結串列、字典、跳躍表、整數集合, 等等,但Redis 並沒有直接使用這些資料結構來實現鍵值對資料庫, 而是基於這些資料結構建立了一個物件系統, 這個系統包含字串物件

Redis5.0原始碼解析(五)----------整數集合

基於redis5.0 整數集合(intset)是集合鍵的底層實現之一: 當一個集合只包含整數值元素, 並且這個集合的元素數量不多時, Redis 就會使用整數集合作為集合鍵的底層實現 redis> SADD numbers 1 3 5 7 9 (integer

Redis5.0原始碼解析(四)----------跳躍表

基於Redis5.0 跳躍表(skiplist)是一種有序資料結構, 它通過在每個節點中維持多個指向其他節點的指標, 從而達到快速訪問節點的目的 跳躍表支援平均 O(log N) 最壞 O(N) 複雜度的節點查詢, 還可以通過順序性操作來批量處理節點。 在大部分

Redis5.0原始碼解析(三)----------字典(詳細)

基於Redis5.0 在字典中, 一個鍵(key)可以和一個值(value)進行關聯(或者說將鍵對映為值), 這些關聯的鍵和值就被稱為鍵值對 字典中的每個鍵都是獨一無二的, 程式可以在字典中根據鍵查詢與之關聯的值, 或者通過鍵來更新值, 又或者根據鍵來刪除整個鍵值對

Redis5.0原始碼解析(二)----------連結串列

基於Redis5.0 連結串列提供了高效的節點重排能力, 以及順序性的節點訪問方式, 並且可以通過增刪節點來靈活地調整連結串列的長度 每個連結串列節點使用一個 adlist.h/listNode 結構來表示: //adlist.h - A generic do

Redis5.0原始碼解析(一)----------簡單動態字串(SDS)

基於Redis5.0 Redis 沒有直接使用 C 語言傳統的字串表示(以空字元結尾的字元陣列,以下簡稱 C 字串), 而是自己構建了一種名為簡單動態字串(simple dynamic string,SDS)的抽象型別, 並將 SDS 用作 Redis 的預設字串表示。

Netty4.0原始碼解析:NioServerSocketChannel

一、引言 Netty的Channel在JDK NIO的Channel基礎上做了一層封裝,提供了更多的功能。Netty的中的Channel實現類主要有:NioServerSocketChannel(用於服務端非阻塞地接收TCP連線)、NioSocketChanne

Netty4.0原始碼解析:位元組容器UnpooledHeapByteBuf

一、引言 Java NIO提供了ByteBuffer作為位元組容器,供Channel讀入和寫入資料。但ByteBuffer使用過於繁瑣,靈活性不夠強。Netty實現了ByteBuf來替代JDK的ByteBuffer。 ByteBuf有以下幾大優點: 1、它可以被

Netty4.0原始碼解析:TCP粘包半包問題的解決方案

一、引言 TCP是一個基於流的協議,TCP作為傳輸層協議並不知道應用層協議的具體含義,它會根據TCP緩衝區的實際情況進行資料包的劃分,所以在應用層上認為是一個完整的包,可能會被TCP拆分成多個包進行傳送,也有可能把多個小的包封裝成一個大的資料包傳送,這就是所謂的

最新版azkaban-3.40.0原始碼解析

web上傳zip以及解析入庫 web服務上傳zip包 入口: azkaban.webapp.servlet.LoginAbstractAzkabanServlet.doPost(HttpServletRequest, HttpServletRes

EventBus3.0原始碼分析

簡述:     在專案中,我們大多數開發者可能都使用過EventBus,即使沒有使用過但我可以確定Android開發者也聽說過這個牛X的庫,從誕生到目前EventBus已經更新到3.X版本,可見生命力極強呀。那麼這篇博文就從EventBus3.0原始碼的角度分析一下其內部處

Redis4.0原始碼解析--3種線性表

為了大家看整體原始碼方便,我將加上了完整註釋的程式碼傳到了我的github上供大家直接下載: 上一章講了SDS動態字串,大概講了看的方向,其實更深層次的還是要請讀者自己看原始碼,我將原始碼加上了註釋,這樣大家看起來也更加的方便,本文講Redis中的連

netty5.0原始碼解析 ByteBuf和相關輔助類

static final class Stack<T> implements Handle<T> { private static final int INITIAL_CAPACITY = 256; final Recycler<T> p

DataX 3.0 原始碼解析

# 原始碼解析 ## 基本呼叫類分析 任務啟動由python指令碼新建程序進行任務執行,後續執行由Java進行,以下將對java部分進行分 其中的呼叫原理機制。 ### Engine 首先入口類為`com.alibaba.datax.core.Engine`的`main`方法,其中通過呼叫

webpack4.0原始碼解析之打包後js檔案分析

首先,init之後建立一個簡單的webpack基本的配置,在src目錄下建立兩個js檔案(一個主入口檔案和一個非主入口檔案)和一個html檔案,package.json,webpack.config.js程式碼如下: var name=require('./index1.js') console.log('

webpack4.0原始碼解析之CommonJS規範打包後js檔案分析

首先,init之後建立一個簡單的webpack基本的配置,在src目錄下建立兩個js檔案(一個主入口檔案和一個非主入口檔案)和一個html檔案,package.json,webpack.config.js程式碼如下: var name=require('./index1.js') console.log('