Android事件總線 EventBus 2.4 源碼分析

EventBus簡(jiǎn)介

本篇基于EventBus 2.4撰寫(xiě)优训。

Android optimized event bus that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality.

上面是從官方repo拉來(lái)的代碼,大致是說(shuō)簡(jiǎn)化的組件之間的交流通信僻爽,減少代碼,提高質(zhì)量。

其實(shí)和EventBus最早是在qzone的代碼里認(rèn)識(shí)的,空間內(nèi)部有一個(gè)叫eventcenter的東西悬槽,曾經(jīng)有優(yōu)化過(guò)一些,當(dāng)時(shí)看源碼實(shí)現(xiàn)的時(shí)候發(fā)現(xiàn)的原來(lái)是根據(jù)EventBus改的一個(gè)實(shí)現(xiàn)想括。大概就是把a(bǔ)nnotation的實(shí)現(xiàn)改成了接口實(shí)現(xiàn)陷谱,另外去掉了根據(jù)Event類(lèi)型來(lái)找訂閱者的模式,完全通過(guò)Event的TYPE類(lèi)型常量來(lái)判斷瑟蜈,register的時(shí)候直接指定對(duì)哪種TYPE感興趣,輔助的判斷則有事件發(fā)送者引用渣窜。這種實(shí)現(xiàn)見(jiàn)仁見(jiàn)智吧铺根,雖然直接通過(guò)接口肯定是能提高性能的。這里要吐槽的是實(shí)現(xiàn)修改的時(shí)候乔宿,直接把很多對(duì)外的接口名字改掉了位迂,何必呢。

EventBus的好處是顯而易見(jiàn)的详瑞,完全解耦了請(qǐng)求鏈之間的關(guān)系掂林,避免了請(qǐng)求者被長(zhǎng)持有,又比廣播更輕量坝橡,比LocalBroadcast則更強(qiáng)大泻帮,接口也簡(jiǎn)單實(shí)用。缺點(diǎn)的話计寇,像是各種Event的定義是一個(gè)工作量锣杂。

源碼分析 - 注冊(cè)(register)

EventBus.java:

private synchronized void register(Object subscriber, boolean sticky, int priority) {
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}

register的時(shí)候脂倦,大致就是去subscriber里面首先找到那些onEvent方法(目前實(shí)現(xiàn)仍然是根據(jù)onEvent這個(gè)前綴),尋找的時(shí)候會(huì)去判斷后綴元莫,分為post線程赖阻、主線程、background線程踱蠢,以及異步線程火欧,官方repo提到這里之后在3.0可能會(huì)換成annotation的實(shí)現(xiàn)。

sticky參數(shù)是粘性事件概念茎截,postSticky和registerSticky相對(duì)應(yīng)布隔,stickyEvent會(huì)記錄該EventType對(duì)應(yīng)的最后一次postSticky的事件,這樣在registerSticky的時(shí)候稼虎,會(huì)立即檢查是否有之前post的事件衅檀,從而避免了某些事件去實(shí)現(xiàn)自己的緩存。應(yīng)用場(chǎng)景大概就是某些activity/fragment感興趣的事件發(fā)生在創(chuàng)建前霎俩,這樣則可以避免必須實(shí)現(xiàn)緩存(當(dāng)然事實(shí)上應(yīng)用場(chǎng)景還是比較少的哀军,因?yàn)榇蟛糠謻|西我們還是會(huì)在哪里記錄一下)

SubscriberMethod.java:

final class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    /** Used for efficient comparison */
    String methodString;
}

SubscriberMethod里面記錄了Method引用,線程模式(在findSubscriberMethods里拿到的)打却,eventType杉适,以及用來(lái)提高method.equals性能的methodString。

接著再看subscribe方法的實(shí)現(xiàn)柳击,在register最后猿推,對(duì)找到的所有方法都去執(zhí)行了一遍subscribe
EventBus.java:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
    Class<?> eventType = subscriberMethod.eventType;

    // CopyOnWriteArrayList就是個(gè)ImmutableArrayList, add/set等方法會(huì)返回一個(gè)新的ArrayList
    // subscriptionsByEventType是一個(gè)hashmap,key是事件類(lèi)型捌肴,value則是訂閱者數(shù)組
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);


    Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);

    // 該eventType在map里還不存在蹬叭,新建一下對(duì)應(yīng)的subscription數(shù)組,放進(jìn)去map
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<Subscription>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        // 重復(fù)注冊(cè)的時(shí)候拋出異常状知,這里如果應(yīng)用如果覺(jué)得無(wú)傷大雅其實(shí)可以直接return
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    // 根據(jù)優(yōu)先級(jí)去添加到對(duì)應(yīng)的位置秽五,高優(yōu)先級(jí)在前面也就會(huì)先處理
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    // typesBySubscriber是另一個(gè)map,顧名思義是以subscriber為key的一個(gè)map饥悴,被用在
    // 1) isRegistered(Object subscriber)方法加速判斷是否已注冊(cè)坦喘,用空間換時(shí)間
    // 2) unregister的時(shí)候直接可以拿到subscriber訂閱的所有eventType,然后去從map移除西设,避免需要遍歷所有eventType的map
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<Class<?>>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 粘性事件的話瓣铣,就去立刻找一下是否有之前post過(guò)的事件,有則立即post給該subscriber
    if (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);
        }
    }
}

源碼分析 - 發(fā)送事件(post)

再來(lái)看一下對(duì)應(yīng)的post邏輯

EventBus.java:

/** Posts the given event to the event bus. */
public void post(Object event) {
    // 獲得當(dāng)前post線程的狀態(tài)贷揽,實(shí)現(xiàn)貼在下面了棠笑,currentPostingThreadState是ThreadLocal<PostingThreadState>變量,每個(gè)線程get和set的都是單獨(dú)的一份數(shù)據(jù)
    PostingThreadState postingState = currentPostingThreadState.get();
    // 往事件隊(duì)列里面添加該event
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    // 如果當(dāng)前不在posting事件
    if (!postingState.isPosting) {
        // 設(shè)置是否在主線程
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        // 設(shè)置當(dāng)前正在post事件
        postingState.isPosting = true;
        // canceled狀態(tài)擒滑,拋出異常
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            // 循環(huán)post從eventQueue里面拿出來(lái)的event
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            // 置位回去
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    // 可以通過(guò)cancelEventDelivery去取消事件傳遞
    boolean canceled;
}

// 單個(gè)事件的post處理
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    // 繼承鏈處理腐晾,比如Event本身的父類(lèi)的subscriber也會(huì)收到叉弦,getDefault的時(shí)候默認(rèn)為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);
    }
    // 竟然沒(méi)找到藻糖,太詭異了
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            // 沒(méi)想到還有這種邏輯吧淹冰,沒(méi)找到訂閱者的,則會(huì)發(fā)送一個(gè)NoSubscriberEvent出去
            post(new NoSubscriberEvent(this, event));
        }
    }
}

// 對(duì)特定的event去post單個(gè)事件
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    // 找到該事件的所有訂閱
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        // 遍歷所有訂閱
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                // 結(jié)果實(shí)際post還在這個(gè)方法內(nèi)實(shí)現(xiàn)
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            // 如果cancel了巨柒,則不再繼續(xù)傳遞事件
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

// 具體的事件分發(fā)
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    // 這里就是EventBus的一個(gè)很強(qiáng)大的功能了樱拴,根據(jù)訂閱者的訂閱方法監(jiān)聽(tīng)線程去處理
    // 如果post和監(jiān)聽(tīng)方法在同一個(gè)線程則立即invoke對(duì)應(yīng)方法
    // 否則會(huì)去入隊(duì)列到對(duì)應(yīng)線程handler進(jìn)行處理
    switch (subscription.subscriberMethod.threadMode) {
        case PostThread:
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BackgroundThread:
            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);
    }
}

End

大致就講了一下register和post這一對(duì)比較常用的接口,其他還有一些實(shí)現(xiàn)像是EventBusBuilder洋满,SubscriberException晶乔,cancelEventDelivery,AsyncExecutor就不在這里進(jìn)行贅述牺勾,之后可能會(huì)對(duì)AsyncExecutor單獨(dú)開(kāi)一篇講一下正罢,另外也會(huì)對(duì)otto的實(shí)現(xiàn)做一下分析。

原文見(jiàn):http://blog.zhaiyifan.cn/2015/08/20/EventBus%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驻民,一起剝皮案震驚了整個(gè)濱河市翻具,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌回还,老刑警劉巖裆泳,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異柠硕,居然都是意外死亡工禾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)蝗柔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闻葵,“玉大人,你說(shuō)我怎么就攤上這事诫咱◇舷叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵坎缭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我签钩,道長(zhǎng)掏呼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任铅檩,我火速辦了婚禮憎夷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昧旨。我一直安慰自己拾给,他們只是感情好祥得,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蒋得,像睡著了一般级及。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上额衙,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天饮焦,我揣著相機(jī)與錄音,去河邊找鬼窍侧。 笑死县踢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伟件。 我是一名探鬼主播硼啤,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斧账!你這毒婦竟也來(lái)了谴返?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤其骄,失蹤者是張志新(化名)和其女友劉穎亏镰,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拯爽,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡索抓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毯炮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逼肯。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖桃煎,靈堂內(nèi)的尸體忽然破棺而出篮幢,到底是詐尸還是另有隱情,我是刑警寧澤为迈,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布三椿,位于F島的核電站,受9級(jí)特大地震影響葫辐,放射性物質(zhì)發(fā)生泄漏搜锰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一耿战、第九天 我趴在偏房一處隱蔽的房頂上張望蛋叼。 院中可真熱鬧,春花似錦、人聲如沸狈涮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)歌馍。三九已至握巢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骆姐,已是汗流浹背镜粤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玻褪,地道東北人肉渴。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像带射,于是被迫代替她去往敵國(guó)和親同规。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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