深入淺出otto——一款輕量的消息總線框架

otto背景

? otto是sqaure推出的一款應(yīng)用主要被應(yīng)用在android上的輕量級(jí)事件總線框架浓冒,目的是為了解決消息通信的問題冰更,他通過(guò)反射幫助你在不持有對(duì)方引用的情況下通知到對(duì)方喜滨,緩解了移動(dòng)開發(fā)中會(huì)遇到的耦合問題廓俭。

otto使用

? otto的使用很簡(jiǎn)單仆葡,下面寫一個(gè)小demo來(lái)演示otto的使用:

class TestBus {
  static Bus INSTANCE;
  public synchronized static Bus getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Bus();
    }
    return INSTANCE;
  }
}
class TestEvent {
  TestEvent() {}
}
class TestSubscriber {
  @Subscribe
  public void onTestEvent(TestEvent event) {
    System.out.println("Get TestEvent");
  }
}
class Test {
  public static void main(String[] args) {
    TestSubscriber subscriber = new TestSubscriber();
    TestBus.getInstance().register(subscriber);
    TestBus.getInstance().post(new TestEvent());
    // output: Get TestEvent
    TestBus.getInstance().unregister(subscriber);
    TestBus.getInstance().post(new TestEvent());
    // output nothing
  }
}

? 類比較少肖卧,但是通過(guò)demo我們可以看到,Test.main并沒有調(diào)用TestSubscriber.onTestEvent方法韭脊,但是onTestEvent方法確實(shí)被執(zhí)行了童谒,這就是otto框架的作用所在。

otto構(gòu)成

? otto框架的構(gòu)成很簡(jiǎn)單沪羔,主要包含以下幾個(gè)類:

  1. Bus
    otto核心類饥伊,主要方法有三:register, post, unregister,用于完成注冊(cè)蔫饰、消息發(fā)送和注銷三個(gè)步驟琅豆。

  2. @Subscribe, @Produce

    用在業(yè)務(wù)代碼中,修飾對(duì)象是方法死嗦,目的是在register(obj)的時(shí)候方便Bus找到event在obj中對(duì)應(yīng)method的注解趋距。

    1. @Subscribe表示obj的方法s注冊(cè)了這個(gè)消息,方法s會(huì)被執(zhí)行
    2. @Produce表示obj的方法p注冊(cè)了這個(gè)消息越除,在subscribe方法執(zhí)行之前节腐,會(huì)執(zhí)行produce方法用來(lái)往event里面塞東西
  3. HandlerFinder

    組合在Bus中,用來(lái)給bus找到注冊(cè)的obj在收到消息event的時(shí)候做什么摘盆,即找到對(duì)應(yīng)的Method對(duì)象翼雀,由Bus緩存在map中,到時(shí)候好直接拿來(lái)用孩擂。

  4. ThreadEnforcer

    組合在Bus中狼渊,只有一個(gè)enforce方法,主要有ThreadEnforcer.Any和ThreadEnforcer.Main兩個(gè)實(shí)現(xiàn)類,

    1. Main用來(lái)檢測(cè)Bus的三大方法是否是在主線程執(zhí)行狈邑,enforce判斷如果不是主線程則拋出異常城须。
    2. Any則沒有限制,方法體為空米苹。
  5. DeadEvent

    在Event沒有接受者的時(shí)候糕伐,Bus會(huì)發(fā)出一個(gè)DeadEvent

otto原理

otto的原理我將從Bus的三個(gè)主要方法分別來(lái)講述他們分別干了什么事情:

  1. register(Object object)

    1. threadEnforcer.enforce,確保如果是主線程的Bus是否是運(yùn)行在主線程蘸嘶,如果不是在主線程則拋出異常

    2. handlerFinder.findAllProducers(object)良瞧,將object里面用@Produce注解且滿足條件的方法(主要包括返回值為Event、沒有參數(shù)等條件)全部加入到producer緩存中去训唱。
      緩存即為producersByType: ConcurrentMap<Class<?>, EventProducer> 褥蚯,其中key是Event.class, value : EventProducer是對(duì)object和他的produce Method的封裝。

    3. handlerFinder.findAllSubscribers(object)况增,同第二步類似

    4. producer.produceEvent赞庶,找到object注冊(cè)的所有消息對(duì)應(yīng)的producers,然后把每個(gè)produce方法都逐個(gè)執(zhí)行一遍巡通。

      注意:這里是produce方法執(zhí)行的唯一時(shí)間點(diǎn)尘执,在post方法中不執(zhí)行produce

  2. post(Object event)

    1. threadEnforcer.enforce,同1.1

    2. flattenHierarchy(event.getClass())宴凉,獲取event的所有基類,也就是說(shuō)event的handler和event基類的handler在后面步驟中都會(huì)被執(zhí)行— 如果存在的話表悬。

    3. 遍歷2.2中獲取的event類和基類弥锄,找到對(duì)應(yīng)的handler,如果有則enqueueEvent入隊(duì)列

    4. 如果2.3沒有找到handler蟆沫,則拋出DeadEvent

    5. dispatch(event, eventHandle)

      1. isDispatching.get()籽暇,isDispatching類型是ThreadLocal<Boolean>,他是用來(lái)判斷當(dāng)前線程的消息隊(duì)列是否正在執(zhí)行饭庞,如果是的話則return

        因?yàn)檎麄€(gè)post執(zhí)行過(guò)程是同步的戒悠,在執(zhí)行過(guò)程a中如果有新時(shí)間進(jìn)來(lái),也會(huì)在a的while循環(huán)中消費(fèi)掉舟山,并且如果是多線程同時(shí)執(zhí)行post會(huì)通過(guò)ThreadLocal保證線程之間不會(huì)有沖突绸狐,所以在這里加這個(gè)判斷是合理的。

      2. while(true) {dispatch}累盗,執(zhí)行掉所有應(yīng)該執(zhí)行的event+handler

  3. unregister(Object object)

    同register步驟類似

otto 性能

? 通過(guò)分析otto的執(zhí)行過(guò)程發(fā)現(xiàn)他是用反射來(lái)做注冊(cè)的寒矿,那么用反射會(huì)不會(huì)帶來(lái)性能損耗呢?下面我就用一個(gè)TestCase來(lái)對(duì)比一下看看若债。對(duì)比的方式就是看一個(gè)onTestEvent方法直接執(zhí)行和用bus.post來(lái)執(zhí)行時(shí)間上有沒有區(qū)別符相,代碼如下:

public class TestOtto {

    private static Bus bus = new Bus(ThreadEnforcer.ANY);

    @Test
    public void testOtto() {
        long startTime = System.currentTimeMillis();
        TestSubscriber subscriber = new TestSubscriber();
        long newTime = System.currentTimeMillis();
        System.out.println("new cost " + (newTime - startTime) + " mills");
        TestEvent event = new TestEvent();
//        for (int i = 0; i < 1000; i++) {
//            bus.post(event);
//        }
        for (int i = 0; i < 1000; i++) {
            subscriber.onTestEvent(event);
        }
        System.out.println("invoke end");
        long endTime = System.currentTimeMillis();
        System.out.println("post cost " + (endTime - startTime) + " mills");
    }

    public static class TestEvent {}

    class TestSubscriber {
        TestSubscriber() {
//            bus.register(this);
        }

        @Subscribe
        public void onTestEvent(TestEvent test) {
            System.out.print("onTestEvent");
        }

    }
}

不使用otto執(zhí)行結(jié)果:

new cost 0 mills
onTestEvent.... end
post cost 22 mills

使用otto執(zhí)行結(jié)果:

new cost 24 mills
onTestEvent... invoke end
post cost 61 mills

? 通過(guò)對(duì)比結(jié)果可以看到,otto的性能同直接調(diào)用相比還是有差距的蠢琳,特別是在注冊(cè)步驟啊终,因?yàn)橐梅瓷浔闅vobject的所有方法镜豹,所以時(shí)間會(huì)拉長(zhǎng)。

otto對(duì)比

? 跟otto框架具備幾乎相同功能的是EventBus框架蓝牲,EventBus是一款跟otto框架具備相同功能趟脂,但是比otto更重的消息總線框架,otto的用法可以原模原樣的搬到EventBus框架上去搞旭,連@Subscribe注解都是用的相同的名字散怖。

? 說(shuō)到兩者的不通電,F(xiàn)rodo的事件總線 —— otto的bus和eventbus對(duì)比分析這篇文章列了兩個(gè)框架的對(duì)比分析肄渗,概括的非常到位:

1镇眷、otto中從源碼角度看,要在基類中注冊(cè)事件是一件比較麻煩的事情翎嫡。而Evenbus就比較友好(有網(wǎng)友反應(yīng)如果父類中注冊(cè)了總線欠动,那么子類中必須實(shí)現(xiàn)一個(gè)onEvent*方法,否則程序就會(huì)崩掉惑申。由于時(shí)間問題沒進(jìn)行驗(yàn)證這一點(diǎn))具伍;
2、訂閱的事件參數(shù)問題圈驼,eventbus對(duì)多參數(shù)不會(huì)拋出異常人芽。而otto只允許接收一個(gè)參數(shù),否則拋出RuntimeException;(其實(shí)這一點(diǎn)作為開源項(xiàng)目對(duì)代碼的質(zhì)量還是挺重要的)
3绩脆、從個(gè)人使用的角度來(lái)看萤厅,個(gè)人更加喜歡otto的特性。因?yàn)槲抑粫?huì)用otto來(lái)簡(jiǎn)化UI的通信靴迫,其他的我并不需要;
4惕味、另外,用java注解的方式來(lái)顯示的標(biāo)記訂閱方法和生產(chǎn)者方法這非常的友好玉锌。至少對(duì)剛使用的開發(fā)者而言名挥,能夠清晰看到代碼的思路;
5、不過(guò)對(duì)不同需求的人群來(lái)說(shuō)主守,eventbus拓展能力和使用場(chǎng)景更加豐富禀倔。如果你的項(xiàng)目通信比較多,而且很復(fù)雜的時(shí)候;
6丸逸、eventbus定義必須onEvent開始的方法感覺還是挺別扭;
7蹋艺、eventbut是不使用注解是因?yàn)樽⒔庠?.3之前的系統(tǒng)上會(huì)變得緩慢(這一點(diǎn)還需要求證一下);
8、在eventbus中有一個(gè)比較難受的地方是:在一個(gè)訂閱者類中如果有兩個(gè)同參數(shù)類型的接收函數(shù)黄刚,并且都要執(zhí)行在主線程,那如何命名呢捎谨?由于EventBus只根據(jù)事件參數(shù)類型來(lái)判斷接收函數(shù),因此會(huì)導(dǎo)致兩個(gè)函數(shù)都會(huì)被執(zhí)行。這當(dāng)然對(duì)開發(fā)者來(lái)說(shuō)比較難受了,不過(guò)github上已經(jīng)有人提出采用添加tag的方式來(lái)做標(biāo)記擴(kuò)展(AndroidEventBus
9涛救、使用otto時(shí)候畏邢,Bus對(duì)象只有作為單例共享的時(shí)候才足夠高效。

otto總結(jié)

? otto框架是一款足夠輕量的消息總線框架检吆,他的使用非常方便舒萎,但是因?yàn)樗谧?cè)的時(shí)候用到了反射,所以性能會(huì)有一定的影響蹭沛,如果你的項(xiàng)目對(duì)性能要求并不那么高臂寝,那么完全可以使用otto框架來(lái)減少你的編碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摊灭,一起剝皮案震驚了整個(gè)濱河市咆贬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帚呼,老刑警劉巖掏缎,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異煤杀,居然都是意外死亡眷蜈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門沈自,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酌儒,“玉大人,你說(shuō)我怎么就攤上這事枯途〗穸梗” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵柔袁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我异逐,道長(zhǎng)捶索,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任灰瞻,我火速辦了婚禮腥例,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酝润。我一直安慰自己燎竖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布要销。 她就那樣靜靜地躺著构回,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纤掸,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天脐供,我揣著相機(jī)與錄音,去河邊找鬼借跪。 笑死政己,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掏愁。 我是一名探鬼主播歇由,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼果港!你這毒婦竟也來(lái)了沦泌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤京腥,失蹤者是張志新(化名)和其女友劉穎赦肃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體公浪,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡他宛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欠气。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厅各。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖预柒,靈堂內(nèi)的尸體忽然破棺而出队塘,到底是詐尸還是另有隱情,我是刑警寧澤宜鸯,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布憔古,位于F島的核電站,受9級(jí)特大地震影響淋袖,放射性物質(zhì)發(fā)生泄漏鸿市。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一即碗、第九天 我趴在偏房一處隱蔽的房頂上張望焰情。 院中可真熱鬧,春花似錦剥懒、人聲如沸内舟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)验游。三九已至充岛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間批狱,已是汗流浹背裸准。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赔硫,地道東北人炒俱。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爪膊,于是被迫代替她去往敵國(guó)和親权悟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理推盛,服務(wù)發(fā)現(xiàn)峦阁,斷路器,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 最近講的都是square出品的組件耘成,今天還是繼續(xù)講解Square旗下的Otto框架 引用otto官網(wǎng)說(shuō)的 Otto...
    亂碼桑閱讀 4,027評(píng)論 2 6
  • 項(xiàng)目到了一定階段會(huì)出現(xiàn)一種甜蜜的負(fù)擔(dān):業(yè)務(wù)的不斷發(fā)展與人員的流動(dòng)性越來(lái)越大榔昔,代碼維護(hù)與測(cè)試回歸流程越來(lái)越繁瑣。這個(gè)...
    fdacc6a1e764閱讀 3,181評(píng)論 0 6
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,094評(píng)論 25 707
  • 大多數(shù)人買東西總會(huì)貨比三家瘪菌,以期物美價(jià)廉撒会。 為了更便于開展業(yè)務(wù),同功能的系統(tǒng)軟件我關(guān)注了兩個(gè): Y...
    想念嗎閱讀 307評(píng)論 0 0