EventBus原始碼分析

BlackFlagBin發表於2018-05-07

EventBus原始碼分析

Android開發中我們最常用到的可以說就是EventBus了,今天我們來深入研究一下EventBus的原始碼。

使用簡介

先簡單回顧下EventBus最基本的使用方法:

  • 首先建立一個資料類
public class MessageEvent {
 
    public final String message;
 
    public MessageEvent(String message) {
        this.message = message;
    }
}
複製程式碼
  • 在相關的程式碼中新增處理事件的方法:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
複製程式碼
  • 在相應的生命週期中註冊/解綁
@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}
 
@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}
複製程式碼
  • 最後一步,在你需要的地方傳送相應的事件
EventBus.getDefault().post(new MessageEvent("Hello world!"));
複製程式碼

原始碼分析

使用EventBus基本需要四步,我們分析原始碼主要著重與後兩步,也就是三個方法: EventBus.getDefault().register(this);EventBus.getDefault().post(new MessageEvent("Hello world!"));EventBus.getDefault().unregister(this);。以這三個方法為入口來切入EventBus的原始碼分析,會使我們對EventBus的整個流程更加清晰。

register方法

public void register(Object subscriber) {
    
         //1.獲取訂閱者的Class物件
        Class<?> subscriberClass = subscriber.getClass();
        
        //通過反射獲取訂閱者中開發人員定義的處理事件的方法集合
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        
        //將獲取的訂閱者和訂閱者中處理事件的方法按照一定的規則相結合並儲存到EventBus內部
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製程式碼

我把register這個方法主要分為三部分。第一步很簡單了,獲取訂閱者的Class物件,這個訂閱者通常就是我們寫的Activity或者Fragment等等。第二步是通過我們第一步拿到的訂閱者的Class物件,反射獲取所有符合規則的處理事件的方法。既然要 符合規則的方法 ,那麼規則是什麼呢?其實規則就是這些方法必須加 @subscribe 註解,引數必須有且只有一個等等。我們來看一下程式碼:

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;
        }
    }
複製程式碼

findSubscriberMethods這個方法中,主要是進行了對訂閱者中我們定義的處理事件的方法的獲取操作。程式碼邏輯其實已經比較清晰了,首先是從快取中拿方法集合,有的話直接返回,沒有的話再去獲取。第二步是獲取的關鍵,它比較複雜,我們暫時先看第三步。如果當前獲取到的方法集合為空,則丟擲異常。看到這裡小夥伴們應該就比較熟悉了,在我們剛開始使用EventBus的時候經常會在一個Activity中呼叫的EventBus的register方法,但是卻忘了寫**@subscribe**註解的方法,執行程式會報錯。那麼很明顯,就是這裡丟擲了一個異常,這個異常提醒我們,如果在訂閱者中呼叫的register方法,就必須新增至少一個相應的處理事件的方法。如果獲取到的方法集合不為空,則將之放入快取並返回。

這個findSubscriberMethods方法中最關鍵的是第二步的獲取。這裡有一個變數 ignoreGeneratedIndex,是否忽略索引,預設值為false,也就是預設不忽略索引,走的是else中的 findUsingInfo(subscriberClass); 方法。那麼問題來了,這個索引是什麼,為什麼我們平常使用EventBus的時候從來都沒用過這個東西?其實在EventBus3.0之前是沒有索引這個東西的,第二步獲取方法集合走的是 findUsingReflection(subscriberClass); 這個方法。那為什麼在3.0之後加上了索引呢?其實答案很簡單,就是因為反射獲取方法集合是比較低效的,使用反射所耗費的時間開銷比較大。如果使用索引,EventBus的效能會提升一倍。具體如何在你的程式碼中使用EventBus的索引,可以參考 greenrobot官網中index的介紹 。那麼小夥伴們問題又來了,既然是預設不忽略索引,而我們平時使用EventBus根本沒新增過索引相關的程式碼,那 findUsingInfo(subscriberClass); 這段程式碼究竟是使用什麼方式來獲取方法集合的呢?放程式碼:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            
            //如果我們沒有新增相關索引的配置,那麼findState.subscriberInfo=null,走的是else中的方法
            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);
    }
複製程式碼

findUsingInfo 這段程式碼中,我們忽略細節,只看我新增註釋的部分。如果新增了索引相關的配置,程式碼會去執行索引相關的邏輯;反之,程式碼執行 findUsingReflectionInSingleClass(findState); 這個函式,通過反射來獲取方法集合。我們忽略索引部分,跟進 findUsingReflectionInSingleClass(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();
            
            //必須是PUBLIC方法.....
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                
                //引數個數只能為1
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    
                    //必須有@subscribe註解
                    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");
            }
        }
    }
複製程式碼

這個方法就很清楚了。首先通過反射拿到所有方法的集合,然後遍歷這個集合,篩選出符合規則的方法,也就是我們寫的處理事件的方法。到這裡小夥伴們應該很清楚了,變數ignoreGeneratedIndex預設是false,走的是索引的處理,但是我平時沒寫過索引的配置,所以最後還是走的反射,通過反射獲取方法集合。

register方法中的第二步到這裡就大致說完了,下面我們開始第三步。程式碼上面有,我再貼一遍方便大家看。

synchronized (this) {
            //遍歷方法集合
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
複製程式碼

這段程式碼很簡單,遍歷第二步獲取的方法集合,呼叫 subscribe(subscriber, subscriberMethod); 這個方法。很明顯,subscribe(subscriber, subscriberMethod); 是最後一步的核心程式碼,我們跟進去看一下:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        
        //獲取方法中的事件型別
        Class<?> eventType = subscriberMethod.eventType;
        
        //將訂閱者和方法綁在一起形成一個Subscription物件
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        
        //將newSubscription加入eventType所對應的subscriptions集合中
        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();
        //按照priority優先順序將newSubscription加入subscriptions集合中
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        //初始化 typesBySubscriber 這個成員變數,形成 訂閱者 -> 訂閱者能夠接收的事件型別集合 的關係
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        //加入新的事件型別
        subscribedEvents.add(eventType);

        //如果方法可以接收sticky事件
        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>).
                
                //遍歷stickEvents這個map
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    
                    //如果找到了跟這個方法的eventType一致的stickEvent
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        
                        //傳送事件,本質是通過反射直接呼叫這個方法
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                //
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
複製程式碼

這段程式碼就比較多了,我們來捋一下它的思路。這個方法接收2個引數Object subscriber, SubscriberMethod subscriberMethod,第一個引數是我們的訂閱者,第二個引數是對我們寫的處理事件的方法的封裝。方法內部首先通過Class<?> eventType = subscriberMethod.eventType;獲取了一個eventType,代表的是處理事件方法中引數的型別,比方說:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
複製程式碼

在這個例子中eventType指的就是MessageEvent.Class。下一步通過Subscription newSubscription = new Subscription(subscriber, subscriberMethod);將訂閱者和方法封裝繫結。接下來的話subscriptionsByEventType這個變數是EventBus中的一個成員變數,通過名字我們也可以知道,這個一個map,它的主要功能就是通過eventType來獲取subscriptions這個Subscription類的集合。我們看一下這個成員變數的定義:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
複製程式碼

跟我們說的一樣,subscriptionsByEventType是一個map,它的key存的是eventType,value存的是Subscription的集合。繼續看下一段程式碼:

 //按照priority優先順序將newSubscription加入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;
            }
        }
複製程式碼

記不記得如果要確保事件的處理需要有先後順序,那麼我們需要在事件處理方法上新增priority的註解。這段程式碼就是把優先順序高的放到list的首部,優先順序低的放到後面,通過這種方式實現了不同方法對同一事件處理的優先順序問題。繼續看程式碼:

//初始化 typesBySubscriber 這個成員變數,形成 訂閱者 -> 訂閱者能夠接收的事件型別集合 的關係
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
複製程式碼

這段程式碼核心是初始化typesBySubscriber這個成員變數,它的結構和subscriptionsByEventType類似,key是subscriber訂閱者,value是eventType的集合。接下來是最後一段程式碼:

 //如果方法可以接收sticky事件
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>).
                
                //遍歷stickEvents這個map
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    
                    //如果找到了跟這個方法的eventType一致或是其子類的stickEvent
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        
                        //傳送事件,本質是通過反射直接呼叫這個方法
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
複製程式碼

粘性事件相信大家都用過,這段程式碼首先判斷了處理事件的方法是否有stick這個註解,如果存在則進入下一步。eventInheritance這個變數預設為true,代表事件是否具有繼承性。可以這麼理解,傳送一個事件或者傳送這個事件型別的子類,EventBus的處理事件方法中的引數型別是父型別,那麼這個方法既可以收到父型別的事件,也可以收到子型別的事件。那小夥伴們要問了,那如果處理事件方法中的引數型別是子型別呢?那這個方法就只能接收子型別的事件,而無法接收父型別的事件。在if (eventType.isAssignableFrom(candidateEventType))這一行程式碼做了判斷,isAssignableFrom這個方法是JDK中Class類中的一個方法,作用就是判斷前者是否是與後者型別一致或前者是後者的父類。下面這個程式碼就很簡單了checkPostStickyEventToSubscription(newSubscription, stickyEvent);傳送事件,我們跟進去看一下:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
        }
    }
複製程式碼

這個方法的功能很簡單,對事件做了非空校驗,然後呼叫了postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());方法,這個方法本質就是通過反射呼叫method.invoke(subscription.subscriber, event);來執行我們定義的對事件的處理方法。這個方法很重要,我會在講post方法的時候著重分析,我們暫時只需要知道它的功能即可。看到這裡,小夥伴們應該就能明白,為什麼在粘性事件發出後再註冊的事件處理方法可以接收到它了,因為之前傳送的stickyEvent都會存入stickyEvents這個map型別的EventBus中的成員變數中,當我們註冊呼叫register方法時,在register方法內部會直接通過checkPostStickyEventToSubscription(newSubscription, stickyEvent);來執行我們定義的粘性事件處理方法。

至此,我們終於說完了register這個方法,這個方法的內容看似只有短短几行,但它所做的事情還是很繁瑣複雜的。下面我們來對上面對register的分析來做個總結:

  • 通過反射獲取訂閱者中定義的處理事件的方法集合
  • 遍歷方法集合,呼叫subscribe方法,在這個subscribe方法內:
    • 初始化並完善subscriptionsByEventType這個map物件
    • 初始化並完善typesBySubscriber這個map物件
    • 觸發stick事件的處理

post方法

我們常用的post有兩種:post和postSticky,先來看postSticky:

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            
            //所有發出的sticky事件都會存入stickyEvents這個map中
            stickyEvents.put(event.getClass(), event);
        }
        // 呼叫普通的post方法
        post(event);
    }
複製程式碼

postSticky結構分為兩塊:將所有發出的sticky事件存入stickyEvents這個map中,關於stickyEvents這個EventBus中的成員變數我們在register方法中已經說過,不再贅述。之後會呼叫post方法,所以我們的重點還是post方法:

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迴圈來不斷髮送事件佇列中的事件,直到事件佇列為空
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
複製程式碼

忽略程式碼細節的話,post還是很好理解的。在傳送事件的當前執行緒中會儲存一個執行緒狀態的變數postingState,這個變數包含了當前執行緒中的一些重要資訊,我們來看一下它的組成:

final static class PostingThreadState {
        //事件佇列
        final List<Object> eventQueue = new ArrayList<Object>();
        //是否正在傳送事件
        boolean isPosting;
        //是否是主執行緒
        boolean isMainThread;
        Subscription subscription;
        Object event;
        //是否已經取消
        boolean canceled;
    }
複製程式碼

可以看到,PostingThreadState為當前執行緒儲存了很多有用的資訊:事件佇列、是否正在傳送事件、是否是主執行緒、subscription、事件、是否已經取消。

繼續看post的程式碼,將傳送的事件加入到了當前執行緒中的事件佇列中。之後就是一個while迴圈,不斷髮送事件佇列中的事件,直到事件佇列中不再包含任何事件。迴圈中傳送事件的方法是postSingleEvent,我們跟進去看一下是如何傳送的:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    
        //獲取事件型別
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        
        //事件是否具有繼承性,預設為true
        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,也就是說在傳送一個子類事件時,EventType是父類的事件處理方法也可以收到當前事件。舉個例子:

@Subscribe()
    public fun onReceiveObject(data: Any) {
        Observable.timer(
                1,
                TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe() {
            toast("any")
        }

    }

    @Subscribe()
    public fun onReceiveDataItem(data: DataItem) {
        Observable.timer(
                2,
                TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe() {
            toast("dataItem")
        }
    }
複製程式碼

這是兩個kotlin寫的事件處理方法,第一個接收的事件型別是Any,對kotlin不熟悉的小夥伴可以理解為Object型別;第二個接收的事件型別是DataItem。我們都知道,Object類肯定是DataItem的父類。我下面提兩個問題,如果傳送Object型別的事件,會發生什麼?如果傳送DataItem型別的事件,會發生什麼?

相信如果大家真正理解了postSingleEvent這個方法的話,這個問題不難回答。如果傳送Object型別,會顯示any的Toast;如果傳送DataItem型別,則會顯示anydataItem的Toast。從上面的程式碼分析我們可以知道,在傳送一個事件的時候,EventBus預設會找到這個事件型別所有的父型別併傳送。

postSingleEvent這個方法中傳送事件的方法是postSingleEventForEventType,我們繼續跟進去看程式碼:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        
        //根據事件型別找到subscriptions
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        
        //變數subscriptions,傳送事件
        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;
    }
複製程式碼

程式碼本事邏輯很簡單:根據subscriptionsByEventType找到事件eventClass對應的subscriptions,相信這些變數大家都很熟悉了。之後會遍歷subscriptions,呼叫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);
        }
    }
複製程式碼

整段程式碼按照執行緒分為四個部分來處理。POSTING代表著當前執行緒,在哪個執行緒傳送事件,就在哪個執行緒處理事件;MAIN代表只在主執行緒處理事件;BACKGROUND代表只在非主執行緒處理事件;ASYNC也是代表在非主執行緒處理事件。

BACKGROUNDASYNC都是在非主執行緒處理事件,那麼二者有什麼區別呢? 從程式碼中可以直觀的看到:BACKGROUND的邏輯是如果在主執行緒,那麼會開啟一條新的執行緒處理事件,如果不在主執行緒,那麼直接處理事件;而ASYNC的邏輯則是不管你處於什麼執行緒,我都新開一條執行緒處理事件。

我們從上到下把這四種型別解釋一下: POSTING中呼叫了invokeSubscriber方法,跟進去看一下:

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);
        }
    }
複製程式碼

核心程式碼只有一句:subscription.subscriberMethod.method.invoke(subscription.subscriber, event);也很好理解,通過反射執行事件處理方法。

MAIN中首先判斷當前執行緒是否是主執行緒,如果是,則執行invokeSubscriber,通過反射直接執行事件處理方法。反之則通過mainThreadPoster.enqueue(subscription, event);來執行事件的處理,那麼它是如何切換執行緒的呢?答案很簡單mainThreadPoster其實是一個主執行緒中的Handler,呼叫mainThreadPoster.enqueue(subscription, event);會觸發它的handleMessage方法,在這個方法中最終呼叫的是eventBus.invokeSubscriber(pendingPost);。可以看到通過handler機制進行了執行緒的切換,在handleMessage中依舊是呼叫invokeSubscriber方法通過反射來執行事件處理方法。

BACKGROUND中首先判斷是否在主執行緒,如果是則呼叫backgroundPoster.enqueue(subscription, event);backgroundPostermainThreadPoster不一樣,backgroundPoster是一個Runnableenqueue實際上是開啟了一個執行緒,在這個新的執行緒中再執行invokeSubscriber這個方法。如果不在主執行緒,則直接呼叫invokeSubscriber

ASYNCasyncPoster和上面提到的backgroundPoster一樣,也是一個Runnable,它的enqueue實際上也是開啟了一個執行緒,在這個新的執行緒中再執行invokeSubscriber這個方法。

至此為止post方法終於講完了,我們來總結一下:

  • 將事件加入當前執行緒的事件佇列
  • 遍歷這個事件佇列
    • 找到當前事件型別所有的父類事件型別,加入事件型別集合並遍歷
      • 通過subscriptionsByEventType獲取subscriptions集合,遍歷這個subscriptions集合
        • 在POSTING、MAIN、BACKGROUND、ASYNC四種執行緒模式下通過反射執行事件處理方法

unregister

終於到最後一個方法了,unregister方法。不廢話,看程式碼:

public synchronized void unregister(Object subscriber) {
    
        //獲取訂閱者所有訂閱的事件型別
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            
            //遍歷事件型別集合
            for (Class<?> eventType : subscribedTypes) {
                
                //從subscriptionsByEventType中刪除訂閱者的相關資訊
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
複製程式碼

這個方法的核心內容就是刪除全域性變數subscriptionsByEventType中所有包含當前訂閱者的subscription,這也就很好的解釋了為什麼在解綁後我們不會再收到EventBus傳送的事件了。因為傳送事件的核心是根據事件型別從subscriptionsByEventType中獲取subscriptions這個集合,遍歷集合通過subscription可以拿到subscribersubscriberMethod,之後再通過subscription.subscriberMethod.method.invoke(subscription.subscriber, event);,採用反射來進行事件的處理。unregister方法刪除了全域性變數subscriptionsByEventType中所有包含當前訂閱者的subscription,在遍歷subscriptions的時候是不會獲取包含當前訂閱者的subscription,所以自然不會再收到事件。

觀察者模式與EventBus

其實看完上面的分析,熟悉觀察者模式的小夥伴們應該很清楚了,搞了這麼多,其實EventBus不就是個觀察者模式麼。沒錯,EventBus的本質就是個觀察者模式,常見的被觀察者只有一個,而觀察者的個數不確定。被觀察者內部維持著一個觀察者的集合,當被觀察者要傳送事件時會遍歷內部的觀察者集合,拿到觀察者並呼叫觀察者的相關方法。觀察者的描述是不是和EventBus的整個流程非常一致?簡而言之,EventBus就是一個被觀察者,它的內部存放著一個subscriptionsByEventType集合,這個集合包含了我們所有的觀察者,也就是呼叫了register的所有Activity或者Fragment等等。當使用post傳送事件時,會遍歷subscriptionsByEventType,拿到觀察者,通過反射呼叫觀察者中的事件處理方法。你沒看錯,EventBus的核心邏輯就是這麼簡單,僅僅只是一個觀察者模式。

再多扯幾句,有些小夥伴可能看完上面一大堆東西還是感覺不是很明白,沒關係,很正常,誰也不太可能任何東西看一遍就瞭然於胸的。我的建議是可以開啟Android Studio邊看原始碼邊看我的分析,不明白的跟進去看原始碼就好。

最後,希望大家能通過這篇分析加深對EventBus和觀察者模式的理解,感謝大家。

相關文章