EventBus源碼解析

? EventBus是Android平臺(tái)上一個(gè)發(fā)布/訂閱事件總線作谚,使用EventBus可以方便的在不同的組件中進(jìn)行消息通信,避免不同組件之間的耦合鸿脓。EventBus或者類似的事件總線基本上是各個(gè)項(xiàng)目中的標(biāo)配抑钟。本文主要基于3.2.0版本介紹EventBus的實(shí)現(xiàn)方式。

EventBus的基本流程

EventBus-Publish-Subscribe.png

? 如上圖所示野哭,EventBus的核心架構(gòu)是通過(guò)post()方法把Event交給EventBus在塔,由EventBus根據(jù)事件的類型,分發(fā)給Subscriber拨黔。Subscriber即訂閱者蛔溃,指的是通過(guò)EventBus的register()方法在EventBus中注冊(cè)的對(duì)象,可以是Activity蓉驹、Fragment也可以是其它對(duì)象城榛。

? EventBus的流程比較簡(jiǎn)單,主要分析幾個(gè)問(wèn)題态兴。

  1. EventBus的注冊(cè)流程
  2. EventBus分發(fā)事件的流程
  3. EventBus怎么切換線程

EventBus如何存儲(chǔ)訂閱關(guān)系

? 在開(kāi)始之前狠持,先介紹一下EventBus存儲(chǔ)訂閱關(guān)系的幾個(gè)Map,了解幾個(gè)Map存儲(chǔ)的數(shù)據(jù)和作用后瞻润,更容易理解EventBus的流程喘垂。

  1. subscriptionsByEventType:
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

變量聲明如上所示俱恶,是一個(gè)保存Event類型和對(duì)應(yīng)的訂閱對(duì)象和方法的Map涧郊,key對(duì)應(yīng)的是Event的Class對(duì)象,value是對(duì)應(yīng)的Subscription的列表。subscriptionsByEventType的作用是根據(jù)Event的類型捶箱,拿到對(duì)應(yīng)的訂閱者列表及其方法缔逛,然后通過(guò)反射的形式進(jìn)行調(diào)用页响。簡(jiǎn)單來(lái)說(shuō)闺魏,subscriptionsByEventType保存的是某個(gè)Event有哪些訂閱者。

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
        ...
}
public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;
    ...
}

Subscription是表示訂閱的實(shí)體類鸭限,從上面兩個(gè)類的代碼可以看出蜕径,Subscription用來(lái)保存訂閱者以及對(duì)應(yīng)的方法,包括方法的對(duì)象败京、線程兜喻、事件類型、優(yōu)先級(jí)等等屬性赡麦。

  1. typesBySubscriber:
private final Map<Object, List<Class<?>>> typesBySubscriber;

typesBySubscriber的key是Subscriber朴皆,value對(duì)應(yīng)的是訂閱的Event的列表。typesBySubscriber的作用是在注冊(cè)成訂閱者或者取消注冊(cè)的時(shí)候泛粹,可以根據(jù)Subscriber拿到對(duì)應(yīng)的Event列表遂铡,然后在subscriptionsByEventType中中方便的移除相應(yīng)的訂閱。與subscriptionsByEventType相反戚扳,typesBySubscriber保存的是某個(gè)訂閱者忧便,訂閱了哪些Event族吻。

EventBus的注冊(cè)流程

? EventBus注冊(cè)的方式很簡(jiǎn)單帽借,通過(guò)register(this)和unregister(this)兩個(gè)方法進(jìn)行注冊(cè)和反注冊(cè),通過(guò)注解的方式標(biāo)識(shí)具體的方法及處理的線程等超歌。

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

register()方法比較好理解砍艾,就是找到subscriber的訂閱的方法,然后挨個(gè)進(jìn)行注冊(cè)巍举。問(wèn)題就在于如何找到訂閱方法脆荷。

遍歷訂閱者的方法

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

在SubscriberMethodFinder中維護(hù)了緩存METHOD_CACHE,保存訂閱者對(duì)應(yīng)的訂閱方法懊悯,提高再次訂閱的效率蜓谋。然后查找訂閱者的方法。EventBus3.0中新增了索引機(jī)制炭分,如果在gradle中配置了索引桃焕,則會(huì)在編譯的時(shí)候就遍歷訂閱者的方法,降低EventBus注冊(cè)占用的時(shí)間捧毛,提升EventBus的性能观堂。

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

通過(guò)內(nèi)部類FindState來(lái)遍歷方法让网,getSubscriberInfo(findState)方法通過(guò)SubscriberInfoIndex獲取訂閱者信息,如果配置了索引师痕,就會(huì)獲取到subscriberInfo溃睹,調(diào)用subscriberInfo.getSubscriberMethods()可以獲取到訂閱的方法數(shù)組。如果沒(méi)有配置索引功能則返回null胰坟,會(huì)通過(guò)反射的方式去遍歷方法因篇。最后從findState獲取訂閱方法的List返回,并重置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) {
           ...
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    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");
            }
        }
    }

這里通過(guò)getDeclaredMethods拿到類的方法惜犀,根據(jù)方法的類型、參數(shù)狠裹、注解依次進(jìn)行篩選虽界,找到符合條件的方法,將方法的相關(guān)信息添加到findState中涛菠。

遍歷完訂閱方法之后莉御,對(duì)訂閱方法進(jìn)行注冊(cè)。

注冊(cè)訂閱方法

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

訂閱的方法中根據(jù)subscriber和subscriberMethod構(gòu)造了一個(gè)Subscription對(duì)象俗冻,然后根據(jù)優(yōu)先級(jí)保存到subscriptionsByEventType中礁叔,在發(fā)送事件的時(shí)候取出依次分發(fā)。把subscriber和事件類型添加到typesBySubscriber中迄薄,方便取消注冊(cè)琅关。如果是方法設(shè)置粘性事件屬性的話,就立刻分發(fā)現(xiàn)存的對(duì)應(yīng)的粘性事件讥蔽。

分發(fā)事件的流程

    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        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()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

把Event放進(jìn)隊(duì)列中涣易,然后調(diào)用postSingleEvent()方法逐個(gè)分發(fā)。

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

調(diào)用lookupAllEventTypes()方法遍歷Event的所有接口和父類冶伞,然后再調(diào)用postSingleEventForEventType()分發(fā)新症。通過(guò)subscriptionsByEventType拿到之前保存的事件的訂閱關(guān)系列表,然后再調(diào)用postToSubscription()方法分發(fā)响禽。

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

根據(jù)ThreadMode屬性進(jìn)行線程的切換徒爹,比如常用的MAIN模式,如果post的線程是主線程芋类,就直接通過(guò)反射的方式調(diào)用訂閱者的訂閱方法隆嗅。如果是在子線程中調(diào)用的post方法,就把這個(gè)訂閱事件加入到mainThreadPoster的隊(duì)列中等待處理侯繁,mainThreadPoster通過(guò)Handler的方式進(jìn)行主線程切換胖喳。

EventBus怎么切換線程

  1. 切換到主線程
public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            ...
            while (true) {
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
}

切換到主線程由HandlerPoster類實(shí)現(xiàn),在enqueue()方法之后巫击,調(diào)用sendMessage觸發(fā)handleMessage方法禀晓,在handleMessage中反射調(diào)用訂閱方法精续。為了避免主線程占用時(shí)間過(guò)長(zhǎng),超過(guò)10ms會(huì)重新通過(guò)sendMessage方法重新安排剩下的反射調(diào)用粹懒。

  1. 切換到子線程
class AsyncPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }

}

切換到子線程通過(guò)AsyncPoster和BackGroudPoster實(shí)現(xiàn)重付,原理是一樣的,繼承Runnable類凫乖,通過(guò)線程池執(zhí)行确垫。在run方法中反射調(diào)用訂閱方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帽芽,一起剝皮案震驚了整個(gè)濱河市删掀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌导街,老刑警劉巖披泪,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搬瑰,居然都是意外死亡款票,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門泽论,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艾少,“玉大人,你說(shuō)我怎么就攤上這事翼悴「抗唬” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鹦赎,是天一觀的道長(zhǎng)谍椅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钙姊,這世上最難降的妖魔是什么毯辅? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮煞额,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沾谜。我一直安慰自己膊毁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布基跑。 她就那樣靜靜地躺著婚温,像睡著了一般。 火紅的嫁衣襯著肌膚如雪媳否。 梳的紋絲不亂的頭發(fā)上栅螟,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天荆秦,我揣著相機(jī)與錄音,去河邊找鬼力图。 笑死步绸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吃媒。 我是一名探鬼主播瓤介,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赘那!你這毒婦竟也來(lái)了刑桑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤募舟,失蹤者是張志新(化名)和其女友劉穎祠斧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拱礁,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梁肿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了觅彰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吩蔑。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖填抬,靈堂內(nèi)的尸體忽然破棺而出烛芬,到底是詐尸還是另有隱情,我是刑警寧澤飒责,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布赘娄,位于F島的核電站,受9級(jí)特大地震影響宏蛉,放射性物質(zhì)發(fā)生泄漏遣臼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一拾并、第九天 我趴在偏房一處隱蔽的房頂上張望揍堰。 院中可真熱鬧,春花似錦嗅义、人聲如沸屏歹。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝙眶。三九已至,卻和暖如春褪那,著一層夾襖步出監(jiān)牢的瞬間幽纷,已是汗流浹背式塌。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留友浸,地道東北人峰尝。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像尾菇,于是被迫代替她去往敵國(guó)和親境析。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前邊文章主要跟大家大概講了下EventBus的用法和注解派诬,接下來(lái)則是從源碼角度來(lái)看EventBus的內(nèi)部處理 Ev...
    Hohohong閱讀 3,169評(píng)論 1 5
  • EventBus是一個(gè)開(kāi)源的事件總線項(xiàng)目劳淆,項(xiàng)目地址:EventBus EventBus通過(guò)注冊(cè)監(jiān)聽(tīng)器和發(fā)布消息的方...
    JasmineBen閱讀 968評(píng)論 1 4
  • 前言 在寫這篇源碼解析到一半時(shí)發(fā)現(xiàn)EventBus有很多高級(jí)用法是不能直接忽略的,于是回過(guò)頭來(lái)寫了EventBus...
    CharmingWong閱讀 425評(píng)論 1 4
  • 項(xiàng)目地址:EventBus默赂,本文分析版本: 3.1.1 一沛鸵、概述 EventBus 是一個(gè) Android 事件發(fā)...
    Yi__Lin閱讀 1,034評(píng)論 1 10
  • 前言:作為一名Android開(kāi)發(fā)人員,EventBus這個(gè)開(kāi)源庫(kù)想必大家在日常的開(kāi)發(fā)工作中都有用到吧缆八。有關(guān)Even...
    Android_Jian閱讀 529評(píng)論 0 4