EventBus原理解析

EventBus是一個Android開源庫具则,其使用發(fā)布/訂閱模式即纲,以提供代碼間的松耦合。EventBus使用中央通信方式博肋,僅僅使用幾行簡單的代碼低斋,就可以達到解耦代碼的目的。從而匪凡,簡化代碼膊畴,移除依賴,加快APP的開發(fā)速度病游。下邊是官方的 EventBus 原理圖:

1唇跨、先說說優(yōu)點:

  • 簡化組件之間的通信
  • 解耦事件的發(fā)送者和接收者
  • 很好的應(yīng)用于Activity之間,F(xiàn)ragment之間衬衬,后臺線程之間的通信买猖,避免使用intent或者h(yuǎn)andler所帶來的復(fù)雜度
  • 可以避免復(fù)雜的,易于出錯的依賴和生命周期問題
  • 速度快佣耐,尤其是在做了優(yōu)化之后
  • 輕量 (大概 50k 的jar包)
  • 有諸多高級特性政勃,例如多種類型的線程模式,subscriber的優(yōu)先級兼砖,等等

2奸远、EventBus特性

  • 簡單易用的基于注解的API:簡單地把@Subscribe注解放在你的訂閱方法前面即可既棺。通過編譯期的訂閱者索引,app不需要在運行期做注解反射懒叛。
  • 支持事件投遞到Android主線程:當(dāng)訂閱者需要和UI做交互的時候丸冕,EventBus能發(fā)送事件到主線程中去,而不管該事件是從哪個線程中發(fā)出來的薛窥。
  • 支持事件投遞到后臺線程:當(dāng)訂閱者需要做耗時任務(wù)時胖烛,EventBus能發(fā)送事件到后臺線程中去,避免阻塞主線程诅迷。
  • 支持事件&訂閱者繼承關(guān)系:在EventBus中佩番,面向?qū)ο笠脖粦?yīng)用到事件和訂閱者類中。例如罢杉,事件類A是事件類B的父類趟畏,這時,發(fā)送B類型的事件滩租,該事件也會發(fā)送給對于事件類A感興趣的訂閱者赋秀。對于訂閱者類,也存在相似的繼承關(guān)系律想。
  • 零設(shè)定即可使用:不需要任何設(shè)定猎莲,在你代碼的任何地方,使用一個現(xiàn)成的默認(rèn)的EventBus實例對象技即,你就可以開始工作了著洼。
  • 可配置:使用建造者模式,你可以調(diào)整EventBus的行為姥份,使之滿足你的需求郭脂。

1年碘、Subscribe注解

EventBus通過使用注解的方式澈歉,配置事件訂閱方法,例如:

@Subscribe
public void handleEvent(String event) {
    // do something
}

其中事件類型可以是 Java 中已有的類型或者我們自定義的類型屿衅。 具體看下Subscribe注解的實現(xiàn):

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 指定事件訂閱方法的線程模式埃难,即在那個線程執(zhí)行事件訂閱方法處理事件,默認(rèn)為POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否支持粘性事件涤久,默認(rèn)為false
    boolean sticky() default false;
    // 指定事件訂閱方法的優(yōu)先級涡尘,默認(rèn)為0,如果多個事件訂閱方法可以接收相同事件的响迂,則優(yōu)先級高的先接收到事件
    int priority() default 0;
}

所以在使用Subscribe注解時可以根據(jù)需求指定threadMode考抄、sticky、priority三個屬性蔗彤。

其中threadMode屬性有如下幾個可選值:

  • ThreadMode.POSTING川梅,默認(rèn)的線程模式疯兼,在哪個線程發(fā)送事件就在哪個線程處理事件,避免了線程切換贫途,效率高吧彪。訂閱方法最好不要執(zhí)行耗時操作,因為它可能會影響發(fā)送者的線程丢早,尤其是主線程的時候

  • ThreadMode.MAIN姨裸,訂閱方法執(zhí)行在主線程。如在主線程(UI線程)發(fā)送事件怨酝,則直接在主線程處理事件傀缩;如果在子線程發(fā)送事件,則先將事件入隊列农猬,然后通過 Handler 切換到主線程扑毡,依次處理事件。

  • ThreadMode.BACKGROUND盛险,訂閱方法執(zhí)行在單一的一個子線程瞄摊。如果在主線程發(fā)送事件,則先將事件入隊列苦掘,然后通過線程池依次處理事件换帜;如果在子線程發(fā)送事件,則直接在發(fā)送事件的線程處理事件鹤啡。

  • ThreadMode.ASYNC惯驼,訂閱方法會在一個新開的子線程(不是主線程、也不是發(fā)送者所在線程)執(zhí)行(類似每次都新建一個線程)递瑰,在執(zhí)行耗時操作時需要使用這個祟牲,不會影響其他線程,但是要控制數(shù)量抖部,避免創(chuàng)建大量線程導(dǎo)致的開銷说贝,EventBus 使用線程池控制

2 注冊事件訂閱方法

注冊事件的方式如下:

EventBus.getDefault().register(this);

其中getDefault()是一個單例方法,保證當(dāng)前只有一個EventBus實例:

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

繼續(xù)看new EventBus()做了些什么:

public EventBus() {
        this(DEFAULT_BUILDER);
    }

在這里又調(diào)用了EventBus的另一個構(gòu)造函數(shù)來完成它相關(guān)屬性的初始化:

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

DEFAULT_BUILDER就是一個默認(rèn)的EventBusBuilder

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

如果有需要的話慎颗,我們也可以通過配置EventBusBuilder來更改EventBus的屬性乡恕,例如用如下方式注冊事件:

EventBus.builder()
        .eventInheritance(false)
        .logSubscriberExceptions(false)
        .build()
        .register(this);

有了EventBus的實例就可以進行注冊了:

public void register(Object subscriber) {
        // 得到當(dāng)前要注冊類的Class對象
        Class<?> subscriberClass = subscriber.getClass();
        // 根據(jù)Class查找當(dāng)前類中訂閱了事件的方法集合,即使用了Subscribe注解俯萎、有public修飾符傲宜、一個參數(shù)的方法
        // SubscriberMethod類主要封裝了符合條件方法的相關(guān)信息:
        // Method對象、線程模式夫啊、事件類型函卒、優(yōu)先級、是否是粘性事等
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 循環(huán)遍歷訂閱了事件的方法集合撇眯,以完成注冊
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

可以看到register()方法主要分為查找和注冊兩部分报嵌,首先來看查找的過程躁愿,從findSubscriberMethods()開始:

2.1 查找過程

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // METHOD_CACHE是一個ConcurrentHashMap,直接保存了subscriberClass和對應(yīng)SubscriberMethod的集合沪蓬,以提高注冊效率彤钟,賦值重復(fù)查找。
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        // 由于使用了默認(rèn)的EventBusBuilder跷叉,則ignoreGeneratedIndex屬性默認(rèn)為false逸雹,即是否忽略注解生成器
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        // 如果對應(yīng)類中沒有符合條件的方法,則拋出異常
        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()流程很清晰云挟,即先從緩存中查找梆砸,如果找到則直接返回,否則去做下一步的查找過程园欣,然后緩存查找到的集合帖世,根據(jù)上邊的注釋可知findUsingInfo()方法會被調(diào)用:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        // 初始狀態(tài)下findState.clazz就是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.clazz為subscriberClass的父類Class,即需要遍歷父類
            findState.moveToSuperclass();
        }
        // 查找到的方法保存在了FindState實例的subscriberMethods集合中沸枯。
        // 使用subscriberMethods構(gòu)建一個新的List<SubscriberMethod>
        // 釋放掉findState
        return getMethodsAndRelease(findState);
    }

findUsingInfo()方法會在當(dāng)前要注冊的類以及其父類中查找訂閱事件的方法日矫,這里出現(xiàn)了一個FindState類,它是SubscriberMethodFinder的內(nèi)部類绑榴,用來輔助查找訂閱事件的方法哪轿,具體的查找過程在findUsingReflectionInSingleClass()方法,它主要通過反射查找訂閱事件的方法:

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;
        }
        // 循環(huán)遍歷當(dāng)前類的方法翔怎,篩選出符合條件的
        for (Method method : methods) {
            // 獲得方法的修飾符
            int modifiers = method.getModifiers();
            // 如果是public類型窃诉,但非abstract、static等
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 獲得當(dāng)前方法所有參數(shù)的類型
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 如果當(dāng)前方法只有一個參數(shù)
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    // 如果當(dāng)前方法使用了Subscribe注解
                    if (subscribeAnnotation != null) {
                        // 得到該參數(shù)的類型
                        Class<?> eventType = parameterTypes[0];
                        // checkAdd()方法用來判斷FindState的anyMethodByEventType map是否已經(jīng)添加過以當(dāng)前eventType為key的鍵值對赤套,沒添加過則返回true
                        if (findState.checkAdd(method, eventType)) {
                             // 得到Subscribe注解的threadMode屬性值飘痛,即線程模式
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            // 創(chuàng)建一個SubscriberMethod對象,并添加到subscriberMethods集合
                            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");
            }
        }
    }

到此register()方法中findSubscriberMethods()流程就分析完了容握,我們已經(jīng)找到了當(dāng)前注冊類及其父類中訂閱事件的方法的集合宣脉。接下來分析具體的注冊流程,即register()中的subscribe()方法:

2.2 訂閱方法

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 得到當(dāng)前訂閱了事件的方法的參數(shù)類型
        Class<?> eventType = subscriberMethod.eventType;
        // Subscription類保存了要注冊的類對象以及當(dāng)前的subscriberMethod
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // subscriptionsByEventType是一個HashMap唯沮,保存了以eventType為key,Subscription對象集合為value的鍵值對
        // 先查找subscriptionsByEventType是否存在以當(dāng)前eventType為key的值
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果不存在脖旱,則創(chuàng)建一個subscriptions,并保存到subscriptionsByEventType
        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);
            }
        }
        // 添加上邊創(chuàng)建的newSubscription對象到subscriptions中
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
          // 按照優(yōu)先級介蛉,插隊隊列中
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
        // typesBySubscribere也是一個HashMap,保存了以當(dāng)前要注冊類的對象為key溶褪,注冊類中訂閱事件的方法的參數(shù)類型的集合為value的鍵值對
        // 查找是否存在對應(yīng)的參數(shù)類型集合
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        // 不存在則創(chuàng)建一個subscribedEvents币旧,并保存到typesBySubscriber
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        // 保存當(dāng)前訂閱了事件的方法的參數(shù)類型
        subscribedEvents.add(eventType);
        // 粘性事件相關(guān)的,后邊具體分析
        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);
            }
        }
    }

這就是注冊的核心流程猿妈,所以subscribe()方法主要是得到了subscriptionsByEventType吹菱、typesBySubscriber兩個 HashMap巍虫。我們在發(fā)送事件的時候要用到subscriptionsByEventType,完成事件的處理鳍刷。當(dāng)取消 EventBus 注冊的時候要用到typesBySubscriber占遥、subscriptionsByEventType,完成相關(guān)資源的釋放输瓜。

3瓦胎、取消注冊

接下來看,EventBus 如何取消注冊:

EventBus.getDefault().unregister(this);

核心的方法就是unregister():

public synchronized void unregister(Object subscriber) {
        // 得到當(dāng)前注冊類對象 對應(yīng)的 訂閱事件方法的參數(shù)類型 的集合
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 遍歷參數(shù)類型集合尤揣,釋放之前緩存的當(dāng)前類中的Subscription
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 刪除以subscriber為key的鍵值對
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

內(nèi)容很簡單搔啊,繼續(xù)看unsubscribeByEventType()方法:

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        // 得到當(dāng)前參數(shù)類型對應(yīng)的Subscription集合
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            // 遍歷Subscription集合
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                // 如果當(dāng)前subscription對象對應(yīng)的注冊類對象 和 要取消注冊的注冊類對象相同,則刪除當(dāng)前subscription對象
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

所以在unregister()方法中北戏,釋放了typesBySubscriber负芋、subscriptionsByEventType中緩存的資源。

4嗜愈、發(fā)送事件

當(dāng)發(fā)送一個事件的時候旧蛾,我們可以通過如下方式:

EventBus.getDefault().post("Hello World!")

可以看到,發(fā)送事件就是通過post()方法完成的:

public void post(Object event) {
        // currentPostingThreadState是一個PostingThreadState類型的ThreadLocal
        // PostingThreadState類保存了事件隊列和線程模式等信息
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 將要發(fā)送的事件添加到事件隊列
        eventQueue.add(event);
        // isPosting默認(rèn)為false
        if (!postingState.isPosting) {
            // 是否為主線程
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                // 遍歷事件隊列
                while (!eventQueue.isEmpty()) {
                    // 發(fā)送單個事件
                    // eventQueue.remove(0)蠕嫁,從事件隊列移除事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

所以post()方法先將發(fā)送的事件保存的事件隊列蚜点,然后通過循環(huán)出隊列,將事件交給postSingleEvent()方法處理:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // eventInheritance默認(rèn)為true拌阴,表示是否向上查找事件的父類
        if (eventInheritance) {
            // 查找當(dāng)前事件類型的Class绍绘,連同當(dāng)前事件類型的Class保存到集合
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            // 遍歷Class集合,繼續(xù)處理事件
            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) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

postSingleEvent()方法中迟赃,根據(jù)eventInheritance屬性陪拘,決定是否向上遍歷事件的父類型,然后用postSingleEventForEventType()方法進一步處理事件:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 獲取事件類型對應(yīng)的Subscription集合
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        // 如果已訂閱了對應(yīng)類型的事件
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                // 記錄事件
                postingState.event = event;
                // 記錄對應(yīng)的subscription
                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;
    }

postSingleEventForEventType()方法核心就是遍歷發(fā)送的事件類型對應(yīng)的Subscription集合纤壁,然后調(diào)用postToSubscription()方法處理事件左刽。

5、處理事件

接著上邊的繼續(xù)分析酌媒,postToSubscription()內(nèi)部會根據(jù)訂閱事件方法的線程模式欠痴,間接或直接的以發(fā)送的事件為參數(shù),通過反射執(zhí)行訂閱事件的方法秒咨。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        // 判斷訂閱事件方法的線程模式
        switch (subscription.subscriberMethod.threadMode) {
            // 默認(rèn)的線程模式喇辽,在那個線程發(fā)送事件就在那個線程處理事件
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            // 在主線程處理事件
            case MAIN:
                // 如果在主線程發(fā)送事件,則直接在主線程通過反射處理事件
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                     // 如果是在子線程發(fā)送事件雨席,則將事件入隊列菩咨,通過Handler切換到主線程執(zhí)行處理事件
                    // mainThreadPoster 不為空
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            // 無論在那個線程發(fā)送事件,都先將事件入隊列,然后通過 Handler 切換到主線程抽米,依次處理事件特占。
            // mainThreadPoster 不為空
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                // 如果在主線程發(fā)送事件,則先將事件入隊列云茸,然后通過線程池依次處理事件
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    // 如果在子線程發(fā)送事件是目,則直接在發(fā)送事件的線程通過反射處理事件
                    invokeSubscriber(subscription, event);
                }
                break;
            // 無論在那個線程發(fā)送事件,都將事件入隊列标捺,然后通過線程池處理懊纳。
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

可以看到,postToSubscription()方法就是根據(jù)訂閱事件方法的線程模式宜岛、以及發(fā)送事件的線程來判斷如何處理事件长踊,至于處理方式主要有兩種:
一種是在相應(yīng)線程直接通過invokeSubscriber()方法,用反射來執(zhí)行訂閱事件的方法萍倡,這樣發(fā)送出去的事件就被訂閱者接收并做相應(yīng)處理了:

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

另外一種是先將事件入隊列(其實底層是一個List)身弊,然后做進一步處理,我們以mainThreadPoster.enqueue(subscription, event)為例簡單的分析下列敲,其中mainThreadPoster是HandlerPoster類的一個實例阱佛,來看該類的主要實現(xiàn):

public class HandlerPoster extends Handler implements Poster {
    private final PendingPostQueue queue;
    private boolean handlerActive;
    ......
    public void enqueue(Subscription subscription, Object event) {
        // 用subscription和event封裝一個PendingPost對象
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // 入隊列
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                // 發(fā)送開始處理事件的消息,handleMessage()方法將被執(zhí)行戴而,完成從子線程到主線程的切換
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            // 死循環(huán)遍歷隊列
            while (true) {
                // 出隊列
                PendingPost pendingPost = queue.poll();
                ......
                // 進一步處理pendingPost
                eventBus.invokeSubscriber(pendingPost);
                ......
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}

所以HandlerPoster的enqueue()方法主要就是將subscription凑术、event對象封裝成一個PendingPost對象,然后保存到隊列里所意,之后通過Handler切換到主線程淮逊,在handleMessage()方法將中將PendingPost對象循環(huán)出隊列,交給invokeSubscriber()方法進一步處理:

void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        // 釋放pendingPost引用的資源
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            // 用反射來執(zhí)行訂閱事件的方法
            invokeSubscriber(subscription, event);
        }
    }

這個方法很簡單扶踊,主要就是從pendingPost中取出之前保存的event泄鹏、subscription,然后用反射來執(zhí)行訂閱事件的方法秧耗,又回到了第一種處理方式备籽。所以mainThreadPoster.enqueue(subscription, event)的核心就是先將將事件入隊列,然后通過Handler從子線程切換到主線程中去處理事件分井。

backgroundPoster.enqueue()和asyncPoster.enqueue也類似车猬,內(nèi)部都是先將事件入隊列,然后再出隊列尺锚,但是會通過線程池去進一步處理事件珠闰。

6、粘性事件

一般情況缩麸,我們使用 EventBus 都是準(zhǔn)備好訂閱事件的方法铸磅,然后注冊事件赡矢,最后在發(fā)送事件杭朱,即要先有事件的接收者阅仔。但粘性事件卻恰恰相反,我們可以先發(fā)送事件弧械,后續(xù)再準(zhǔn)備訂閱事件的方法八酒、注冊事件。

由于這種差異刃唐,我們分析粘性事件原理時羞迷,先從事件發(fā)送開始,發(fā)送一個粘性事件通過如下方式:

EventBus.getDefault().postSticky("Hello World!");

來看postSticky()方法是如何實現(xiàn)的:

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        post(event);
    }

postSticky()方法主要做了兩件事画饥,先將事件類型和對應(yīng)事件保存到stickyEvents中衔瓮,方便后續(xù)使用;然后執(zhí)行post(event)繼續(xù)發(fā)送事件抖甘,這個post()方法就是之前發(fā)送的post()方法热鞍。所以,如果在發(fā)送粘性事件前衔彻,已經(jīng)有了對應(yīng)類型事件的訂閱者薇宠,及時它是非粘性的,依然可以接收到發(fā)送出的粘性事件艰额。

發(fā)送完粘性事件后澄港,再準(zhǔn)備訂閱粘性事件的方法,并完成注冊柄沮。核心的注冊事件流程還是我們之前的register()方法中的subscribe()方法回梧,前邊分析subscribe()方法時,有一段沒有分析的代碼祖搓,就是用來處理粘性事件的:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ......
        ......
        ......
        // 如果當(dāng)前訂閱事件的方法的Subscribe注解的sticky屬性為true狱意,即該方法可接受粘性事件
        if (subscriberMethod.sticky) {
            // 默認(rèn)為true,表示是否向上查找事件的父類
            if (eventInheritance) {
                // stickyEvents就是發(fā)送粘性事件時棕硫,保存了事件類型和對應(yīng)事件
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    // 如果candidateEventType是eventType的子類或
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        // 獲得對應(yīng)的事件
                        Object stickyEvent = entry.getValue();
                        // 處理粘性事件
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

可以看到髓涯,處理粘性事件就是在 EventBus 注冊時,遍歷stickyEvents哈扮,如果當(dāng)前要注冊的事件訂閱方法是粘性的纬纪,并且該方法接收的事件類型和stickyEvents中某個事件類型相同或者是其父類,則取出stickyEvents中對應(yīng)事件類型的具體事件滑肉,做進一步處理包各。繼續(xù)看checkPostStickyEventToSubscription()處理方法:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

最終還是通過postToSubscription()方法完成粘性事件的處理,這就是粘性事件的整個處理流程靶庙。

7问畅、Subscriber Index

回顧之前分析的 EventBus 注冊事件流程,主要是在項目運行時通過反射來查找訂事件的方法信息,這也是默認(rèn)的實現(xiàn)护姆,如果項目中有大量的訂閱事件的方法矾端,必然會對項目運行時的性能產(chǎn)生影響。其實除了在項目運行時通過反射查找訂閱事件的方法信息卵皂,EventBus 還提供了在項目編譯時通過注解處理器查找訂閱事件方法信息的方式秩铆,生成一個輔助的索引類來保存這些信息,這個索引類就是Subscriber Index灯变,其實和 ButterKnife 的原理類似殴玛。

要在項目編譯時查找訂閱事件的方法信息,首先要在 app 的 build.gradle 中加入如下配置:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根據(jù)項目實際情況添祸,指定輔助索引類的名稱和包名
                arguments = [ eventBusIndex : 'com.shh.sometest.MyEventBusIndex' ]
            }
        }
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    // 引入注解處理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

然后在項目的 Application 中添加如下配置滚粟,以生成一個默認(rèn)的 EventBus 單例:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

之后的用法就和我們平時使用 EventBus 一樣了。當(dāng)項目編譯時會在生成對應(yīng)的MyEventBusIndex類:

對應(yīng)的源碼如下:

public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("changeText", String.class),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

其中SUBSCRIBER_INDEX是一個HashMap刃泌,保存了當(dāng)前注冊類的 Class 類型和其中事件訂閱方法的信息凡壤。

接下來分析下使用 Subscriber Index 時 EventBus 的注冊流程,我們先分析:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

首先創(chuàng)建一個EventBusBuilder蔬咬,然后通過addIndex()方法添加索引類的實例:

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

即把生成的索引類的實例保存在subscriberInfoIndexes集合中鲤遥,然后用installDefaultEventBus()創(chuàng)建默認(rèn)的 EventBus實例:

public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }
public EventBus build() {
        // this 代表當(dāng)前EventBusBuilder對象
        return new EventBus(this);
    }

即用當(dāng)前EventBusBuilder對象創(chuàng)建一個 EventBus 實例,這樣我們通過EventBusBuilder配置的 Subscriber Index 也就傳遞到了EventBus實例中林艘,然后賦值給EventBus的 defaultInstance成員變量盖奈。之前我們在分析 EventBus 的getDefault()方法時已經(jīng)見到了defaultInstance:

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

所以在 Application 中生成了 EventBus 的默認(rèn)單例,這樣就保證了在項目其它地方執(zhí)行EventBus.getDefault()就能得到唯一的 EventBus 實例狐援!之前在分析注冊流程時有一個 方法findUsingInfo():

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 查找SubscriberInfo
            findState.subscriberInfo = getSubscriberInfo(findState);
            // 條件成立
            if (findState.subscriberInfo != null) {
                // 獲得當(dāng)前注冊類中所有訂閱了事件的方法
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    // findState.checkAdd()之前已經(jīng)分析過了钢坦,即是否在FindState的anyMethodByEventType已經(jīng)添加過以當(dāng)前eventType為key的鍵值對,沒添加過返回true
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        // 將subscriberMethod對象添加到subscriberMethods集合
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

由于我們現(xiàn)在使用了 Subscriber Index 所以不會通過findUsingReflectionInSingleClass()來反射解析訂閱事件的方法啥酱。我們重點來看getSubscriberInfo()都做了些什么:


private SubscriberInfo getSubscriberInfo(FindState findState) {
        // 該條件不成立
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        // 該條件成立
        if (subscriberInfoIndexes != null) {
            // 遍歷索引類實例集合
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                // 根據(jù)注冊類的 Class 類查找SubscriberInfo
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

subscriberInfoIndexes就是在前邊addIndex()方法中創(chuàng)建的爹凹,保存了項目中的索引類實例,即MyEventBusIndex的實例镶殷,繼續(xù)看索引類的getSubscriberInfo()方法禾酱,來到了MyEventBusIndex類中:

@Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }

即根據(jù)注冊類的 Class 類型從 SUBSCRIBER_INDEX 查找對應(yīng)的SubscriberInfo,如果我們在注冊類中定義了訂閱事件的方法绘趋,則 info不為空颤陶,進而上邊f(xié)indUsingInfo()方法中findState.subscriberInfo != null成立,到這里主要的內(nèi)容就分析完了陷遮,其它的和之前的注冊流程一樣滓走。
所以 Subscriber Index 的核心就是項目編譯時使用注解處理器生成保存事件訂閱方法信息的索引類,然后項目運行時將索引類實例設(shè)置到 EventBus 中帽馋,這樣當(dāng)注冊 EventBus 時搅方,從索引類取出當(dāng)前注冊類對應(yīng)的事件訂閱方法信息比吭,以完成最終的注冊,避免了運行時反射處理的過程姨涡,所以在性能上會有質(zhì)的提高衩藤。項目中可以根據(jù)實際的需求決定是否使用 Subscriber Index。

這里提一句绣溜,使用這種方式慷彤,會在查找過程中娄蔼,使用索引的方式添加subscriberMethods怖喻,避免了反射使用clazz.getDeclaredMethods();方式,從而提升了性能岁诉,但是post過程锚沸,最終調(diào)用,還是會method.invoke()的方式來調(diào)用方法涕癣。

而且clazz.getDeclaredMethods()耗時會比method.invoke()多一些哗蜈,大約在2:1的時間,當(dāng)然坠韩,如果次數(shù)少的話距潘,沒啥差別,如果次數(shù)多了只搁,就會有影響音比。之前做過實驗贷币,調(diào)用100萬次的話苛萎,clazz.getMethods()是3242ms,method.invoke()是1632ms沮翔。

8 小結(jié)

結(jié)合上邊的分析焰望,我們可以總結(jié)出register骚亿、post、unregister的核心流程:

到這里 EventBus 幾個重要的流程就分析完了熊赖,整體的設(shè)計思路還是值得我們學(xué)習(xí)的来屠。和 Android 自帶的廣播相比,使用簡單震鹉、同時又兼顧了性能俱笛。但如果在項目中濫用的話,各種邏輯交叉在一起足陨,也可能會給后期的維護帶來困難嫂粟。



聲明:此文章為本人學(xué)習(xí)筆記,參考于:https://juejin.im/post/5ae2e6dcf265da0b9d77f28e#heading-1

如果您覺得有用墨缘,歡迎關(guān)注我的公眾號星虹,我會不定期發(fā)布自己的學(xué)習(xí)筆記零抬、資料、以及感悟宽涌,歡迎留言平夜,與大家一起探索AI之路。

AI探索之路
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卸亮,一起剝皮案震驚了整個濱河市忽妒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼贸,老刑警劉巖段直,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異溶诞,居然都是意外死亡鸯檬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門螺垢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喧务,“玉大人,你說我怎么就攤上這事枉圃」睿” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵孽亲,是天一觀的道長坎穿。 經(jīng)常有香客問我,道長墨林,這世上最難降的妖魔是什么赁酝? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮旭等,結(jié)果婚禮上酌呆,老公的妹妹穿的比我還像新娘。我一直安慰自己搔耕,他們只是感情好隙袁,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弃榨,像睡著了一般菩收。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲸睛,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天娜饵,我揣著相機與錄音,去河邊找鬼官辈。 笑死箱舞,一個胖子當(dāng)著我的面吹牛遍坟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晴股,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼愿伴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了电湘?” 一聲冷哼從身側(cè)響起隔节,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寂呛,沒想到半個月后怎诫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡昧谊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年刽虹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呢诬。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胖缤,靈堂內(nèi)的尸體忽然破棺而出尚镰,到底是詐尸還是另有隱情,我是刑警寧澤哪廓,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布狗唉,位于F島的核電站,受9級特大地震影響涡真,放射性物質(zhì)發(fā)生泄漏分俯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一哆料、第九天 我趴在偏房一處隱蔽的房頂上張望缸剪。 院中可真熱鬧,春花似錦东亦、人聲如沸杏节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奋渔。三九已至,卻和暖如春壮啊,著一層夾襖步出監(jiān)牢的瞬間嫉鲸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工歹啼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玄渗,地道東北人减江。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像捻爷,于是被迫代替她去往敵國和親辈灼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361