EventBus 3.0 原始碼分析

philadelphia發表於2022-03-15

EventBus 3.0原始碼分析

簡介

EvenntBus 是一個Android開發中的用於事件分發的開源庫。它的工作核心是釋出/訂閱者者模式,它可以利用很少的程式碼來實現多元件間通訊。android的元件間通訊,我們不由得會想到handler訊息機制和廣播機制,通過它們也可以進行通訊,但是使用它們進行通訊,程式碼量多,元件間容易產生耦合引用。關於EventBus的工作模式,這裡引用一張官方圖幫助理解。
EventBus

為什麼會選擇使用EventBus來做通訊?

  • 簡化了元件間交流的方式
  • 對事件通訊雙方進行解耦
  • 可以靈活方便的指定工作執行緒,通過ThreadMode
  • 速度快,效能好
  • 庫比較小,60k左右,對包大小無影響
  • 使用這個庫的app多,有權威性
  • 功能多,使用方便

EventBus的使用也非常簡單,其中三個重要的角色

1:Publisher 事件釋出者

2:Subscriber 事件訂閱者

3:Event 事件

Publisher post 事件後,Subscriber會自動收到事件(訂閱方法會被主動呼叫,並將事件傳遞過來)。

使用

 //倒入gradle 依賴
 implementation 'org.greenrobot:eventbus:3.3.1'

1:定義事件型別

public static class MessageEvent { /* Additional fields if needed */ }

2:在需要訂閱事件的模組中註冊EventBus,頁面銷燬時注意登出

 @Override
 protected void onStart() {
       super.onStart();
    EventBus.getDefault().register(this);    
 }

@Override
protected void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
}

3:註冊需要接受的事件型別 //注意同一種事件型別不能重複註冊。不然會崩潰,且訂閱方法必須是public型別的。

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {
    // Do something
}

4.傳送事件

EventBus.getDefault().post(new MessageEvent());
這是步驟3種的方法就會收到MessageEvent事件的回撥

原始碼分析

EventBus 主類中只有不到600行程式碼,非常精簡。EventBus使用了對外提供了單例模型,內部構建使用了Build模式。

1 register

public void register(Object subscriber) {
    if (AndroidDependenciesDetector.isAndroidSDKAvailable() &&     !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
        // Crash if the user (developer) has not imported the Android compatibility library.
        throw new RuntimeException("It looks like you are using EventBus on Android, " +
                "make sure to add the \"eventbus\" Android library to your dependencies.");
    }

        //1從這裡開始看,獲取呼叫者的類物件。
    Class<?> subscriberClass = subscriber.getClass();
    //2 找到訂閱類中的訂閱方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    
    //3遍歷訂閱方法,將訂閱者和其中的訂閱方法繫結。
       synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

Step1

看下步驟2中的subscriberMethodFinder的findSubscriberMethods方法

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {    
        //1首先從快取map中拿到訂閱類的訂閱方法列表,使用了快取提高效能,nice,不出所料METHOD_CACHE的型別是Map<Class<?>, List<SubscriberMethod> >
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    //2 如果不為空,說明之前該類曾經註冊過,該類的新物件不必重新做繫結了,因為此時的操作是類層面的
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
        //如果subscriberMethods 為null,說明該類是第一次註冊,需要將其中的接收方法儲存起來,
      //ignoreGeneratedIndex 預設為false
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    //如果subscriberMethods為null,說明當前類物件沒有生命訂閱方法,丟擲異常
    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;
    }
}

Step2

從步驟2中找出類的註冊方法列表,然後遍歷列表,呼叫下面的方法,將類物件和註冊方法繫結。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //1 找到訂閱方法的事件型別,即傳送事件的MessageEvent.class
    Class<?> eventType = subscriberMethod.eventType;
  
      //2 將訂閱者類物件和訂閱事件繫結成一個物件
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //subscriptionsByEventType 這個集合肯定是用來放置同一事件型別的訂閱集合的,因為一個事件可能會有多個訂閱的。
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
            //如果一個訂閱者多次訂閱了一個事件(@Subscribe註解的方法的引數是同一型別),丟擲異常
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

        //3 按照訂閱方法中@Subscribe中的priority引數進行排序,預設為最低優先順序0。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;
        }
    }

        // typesBySubscriber型別Map<Object, List<Class<?>>>,Key 為訂閱者,value為訂閱者中的訂閱方法,用來記錄每個訂閱者內部都訂閱了哪些事件型別
    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);
        }
    }
}

2 post

/** Posts the given event to the event bus. */
public void post(Object event) {
        //1首先獲取當前執行緒的工作狀態
    PostingThreadState postingState = currentPostingThreadState.get();
    //2獲取當前執行緒的任務佇列
    List<Object> eventQueue = postingState.eventQueue;
    //3 將事件加入到事件佇列
    eventQueue.add(event);
     //4 如果當前執行緒的工作狀態沒有正在傳送事件 
    if (!postingState.isPosting) {
            //標記postingState 的是否是主執行緒,並將工作狀態isPosting 設為true
        postingState.isMainThread = isMainThread();
        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;
        }
    }
}


/**
* 傳送事件
* @param event 事件
* @param postingState 當前執行緒相關的配置
*/
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //如果使用繼承事件的父類/介面,比如你傳送了MessageEvent 事件,如果該事件繼承了BaseEvent和Ievent介面,那麼當你傳送                        MessageEvent 事件時,系統也會傳送BaseEvent和Ieven事件
        if (eventInheritance) {
                //遍歷父類,將事件的父類/介面統統加入到eventTypes中
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                //遍歷eventTypes,依次傳送呼叫事件
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
           
        } else {
                //不實用事件繼承模型,直接傳送該事件
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
                //如果該事件沒有訂閱者丟擲異常
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            //EventBus 內部也適用EventBus 傳送了一個異常事件
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
    

postSingleEventForEventType

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //1獲取該事件的所有訂閱關係列表
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    //2 遍歷訂閱關係列表,依次將事件傳送到訂閱者
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted;
            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

這裡就比較關鍵了,最終到了事件分發的地方了。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        //1 首先判斷訂閱關係中訂閱方法的執行緒,就是宣告執行緒時使用@Subcribe註解時傳入的threadMode欄位的值
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING: //直接傳送事件
            invokeSubscriber(subscription, event);
            break;
        case MAIN:  //在主執行緒相應事件
                    //事件發出執行緒是否是主執行緒
            if (isMainThread) { 是,直接傳送
                invokeSubscriber(subscription, event);
            } else {不是通過mainThreadPoster傳送
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND: //在後臺執行緒相應事件
                 //事件發出執行緒是否是主執行緒
            if (isMainThread) {主執行緒傳送事件,backgroundPoster轉發
                backgroundPoster.enqueue(subscription, event);
            } else { 非主執行緒傳送事件,直接傳送
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC: //非同步執行緒相應事件
                //通過asyncPoster傳送事件
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

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

在postToSubscription中有三個重要的角色mainThreadPoster,backgroundPoster,asyncPoster

其中mainThreadPoster的型別是HandlerPoster。其實就是Handler。呼叫其enqueu()方法

而backgroundPoster和asyncPoster 本質都是Runnable

3 unregister

解綁方法就簡單多了,重要的是就把register裡提到的2個重要的幾何中刪除訂閱者

/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
    //1 從typesBySubscriber找到訂閱者所訂閱的事件型別列表
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //2 遍歷列表,依次解綁訂閱者和事件型別。應該是從post分析裡的訂閱事件集合subscriptionsByEventType裡移除對應事件型別的該訂閱者
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //3移除訂閱者
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

/**
* 解綁訂閱者和事件型別
* @param subscriber 訂閱者
* @param eventType  訂閱的事件型別
*/
 private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
                //從subscriptionsByEventType裡獲取該訂閱事件的訂閱者集合。
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                    //遍歷集合,獲取所有的訂閱關係
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    //重要。不然會丟擲ConcurrentModifyException
                    i--;
                    size--;
                }
            }
        }
    }

4 Subscribe註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default 0;
}

5 ThreadMode

//定義事件回撥方法工作執行緒的類
public enum ThreadMode {
    /**
     *直接在傳送事件的執行緒裡呼叫Subscriber,這個是預設的設定,事件交付開銷最小,因為它避免了執行緒切換。因此它是那種很快完成的單任務             *預設的的執行緒工作模型。使用該模型的事件必須很快完成,因為當釋出執行緒是主執行緒時,它可能阻塞主執行緒。
     /
    POSTING,

    /**
     * 在Android平臺,訂閱者將會在Android的主執行緒呼叫。如果釋出執行緒時主執行緒,訂閱方法將會被直接呼叫。進而阻賽釋出執行緒,如果釋出線             * 程不是主執行緒。事件將會排隊等待分發。使用這種模式的訂閱者必須快速完成任務,避免阻賽主執行緒。非Android平臺和Posting一樣
     */
    MAIN,

    /**
     * 在Android平臺,訂閱者將會在Android的主執行緒呼叫。不同於MAIN,事件將會有序分發。確保了post呼叫時非阻賽的。
     */
    MAIN_ORDERED,

    /** 
     * 在Android平臺,訂閱者將會在後臺執行緒被呼叫,如果釋出執行緒不是主執行緒,訂閱者將會被直接呼叫,如果釋出執行緒時主執行緒,那麼EventBus             * 使用後臺執行緒,進而有序分發所有事件,使用此模式的Subscribers應該快速完成任務以免阻賽後臺執行緒。非Android平臺,總是用後臺執行緒                   * 相應事件 
     */
    BACKGROUND,

    /**
     * 訂閱者將會在單獨的執行緒被呼叫,總是獨立於釋出執行緒和主執行緒。釋出事件從不會等待使用這種模式的訂閱方法。如果訂閱方法執行耗時任務,         * 則應該使用此模式。比如;網路訪問。避免同時觸發大量的長時間執行的非同步訂閱方法,從而限制併發的執行緒數量。EventBus 使用執行緒池來            * 高效的服用已完成非同步訂閱通知的執行緒
     */
    ASYNC
}

6 EventBus2.0和EventBus3.0的區別?

這是在面試過程中,面試官最常問的一個問題。

EventBus2.0和3.0最大的區別有兩點:

1.EventBus2.0中我們在書寫訂閱方法時的名字必須是onEvent開頭,然後通過命名不同來區別不同的執行緒模式。例如對應posting則命名為onEvent(),onEventMainThread()則對應main等。而3.0則可以用任何名字作為方法名稱,只需要在方法名的前面用@Subscribe註解來進行註釋,然後使用threadMode來設定在哪裡執行緒中接收事件和處理事件

2.EventBus2.0使用的是反射的方式來查詢所有的訂閱方法,而3.0則是在編譯時通過註解處理器的方式來查詢所有的訂閱方法。效能上來說,3.0比2.0要高的多。

相關文章