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è)類:
Bus
otto核心類饥伊,主要方法有三:register, post, unregister,用于完成注冊(cè)蔫饰、消息發(fā)送和注銷三個(gè)步驟琅豆。-
@Subscribe, @Produce
用在業(yè)務(wù)代碼中,修飾對(duì)象是方法死嗦,目的是在register(obj)的時(shí)候方便Bus找到event在obj中對(duì)應(yīng)method的注解趋距。
- @Subscribe表示obj的方法s注冊(cè)了這個(gè)消息,方法s會(huì)被執(zhí)行
- @Produce表示obj的方法p注冊(cè)了這個(gè)消息越除,在subscribe方法執(zhí)行之前节腐,會(huì)執(zhí)行produce方法用來(lái)往event里面塞東西
-
HandlerFinder
組合在Bus中,用來(lái)給bus找到注冊(cè)的obj在收到消息event的時(shí)候做什么摘盆,即找到對(duì)應(yīng)的Method對(duì)象翼雀,由Bus緩存在map中,到時(shí)候好直接拿來(lái)用孩擂。
-
ThreadEnforcer
組合在Bus中狼渊,只有一個(gè)enforce方法,主要有ThreadEnforcer.Any和ThreadEnforcer.Main兩個(gè)實(shí)現(xiàn)類,
- Main用來(lái)檢測(cè)Bus的三大方法是否是在主線程執(zhí)行狈邑,enforce判斷如果不是主線程則拋出異常城须。
- Any則沒有限制,方法體為空米苹。
-
DeadEvent
在Event沒有接受者的時(shí)候糕伐,Bus會(huì)發(fā)出一個(gè)DeadEvent
otto原理
otto的原理我將從Bus的三個(gè)主要方法分別來(lái)講述他們分別干了什么事情:
-
register(Object object)
threadEnforcer.enforce,確保如果是主線程的Bus是否是運(yùn)行在主線程蘸嘶,如果不是在主線程則拋出異常
handlerFinder.findAllProducers(object)良瞧,將object里面用@Produce注解且滿足條件的方法(主要包括返回值為Event、沒有參數(shù)等條件)全部加入到producer緩存中去训唱。
緩存即為producersByType: ConcurrentMap<Class<?>, EventProducer> 褥蚯,其中key是Event.class, value : EventProducer是對(duì)object和他的produce Method的封裝。handlerFinder.findAllSubscribers(object)况增,同第二步類似
-
producer.produceEvent赞庶,找到object注冊(cè)的所有消息對(duì)應(yīng)的producers,然后把每個(gè)produce方法都逐個(gè)執(zhí)行一遍巡通。
注意:這里是produce方法執(zhí)行的唯一時(shí)間點(diǎn)尘执,在post方法中不執(zhí)行produce
-
post(Object event)
threadEnforcer.enforce,同1.1
flattenHierarchy(event.getClass())宴凉,獲取event的所有基類,也就是說(shuō)event的handler和event基類的handler在后面步驟中都會(huì)被執(zhí)行— 如果存在的話表悬。
遍歷2.2中獲取的event類和基類弥锄,找到對(duì)應(yīng)的handler,如果有則enqueueEvent入隊(duì)列
如果2.3沒有找到handler蟆沫,則拋出DeadEvent
-
dispatch(event, eventHandle)
-
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è)判斷是合理的。
while(true) {dispatch}累盗,執(zhí)行掉所有應(yīng)該執(zhí)行的event+handler
-
-
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)減少你的編碼。