先吐槽一下博客園的MarkDown編輯器钥组,推出的時候還很高興博客園支持MarkDown了输硝,試用了下發(fā)現(xiàn)支持不完善就沒用了,這次這篇是在其他編輯器下寫的程梦,復(fù)制過來后發(fā)現(xiàn)点把。橘荠。太爛了。怎么著作為一個技術(shù)博客社區(qū)郎逃,對代碼的支持應(yīng)該完善一下吧哥童,`行內(nèi)代碼塊`不支持就算了,代碼段內(nèi)還不能有空行褒翰,一有空行就識別不了了贮懈。而且試著用MarkDown發(fā)了篇草稿,右邊的側(cè)邊欄竟然被擠到屏幕下方了优训,還影響到了博客布局朵你。。不說了揣非。抡医。簡單修改下標(biāo)題、代碼直接發(fā)表早敬。
概述及基本概念
**EventBus**是一個Android端優(yōu)化的publish/subscribe消息總線忌傻,簡化了應(yīng)用程序內(nèi)各組件間、組件與后臺線程間的通信搁嗓。比如請求網(wǎng)絡(luò),等網(wǎng)絡(luò)返回時通過Handler或Broadcast通知UI箱靴,兩個Fragment之間需要通過Listener通信腺逛,這些需求都可以通過**EventBus**實(shí)現(xiàn)。
作為一個消息總線衡怀,有三個主要的元素:
Event:事件
Subscriber:事件訂閱者棍矛,接收特定的事件
Publisher:事件發(fā)布者,用于通知Subscriber有事件發(fā)生
Event
**Event**可以是任意類型的對象抛杨。
Subscriber
在EventBus中够委,使用約定來指定事件訂閱者以簡化使用。即所有事件訂閱都都是以onEvent開頭的函數(shù)怖现,具體來說茁帽,函數(shù)的名字是onEvent,onEventMainThread屈嗤,onEventBackgroundThread潘拨,onEventAsync這四個,這個和ThreadMode有關(guān)饶号,后面再說铁追。
Publisher
可以在任意線程任意位置發(fā)送事件,直接調(diào)用EventBus的`post(Object)`方法茫船,可以自己實(shí)例化EventBus對象琅束,但一般使用默認(rèn)的單例就好了:`EventBus.getDefault()`扭屁,根據(jù)post函數(shù)參數(shù)的類型,會自動調(diào)用訂閱相應(yīng)類型事件的函數(shù)涩禀。
ThreadMode
前面說了料滥,Subscriber函數(shù)的名字只能是那4個,因?yàn)槊總€事件訂閱函數(shù)都是和一個`ThreadMode`相關(guān)聯(lián)的埋泵,ThreadMode指定了會調(diào)用的函數(shù)幔欧。有以下四個ThreadMode:
PostThread:事件的處理在和事件的發(fā)送在相同的進(jìn)程,所以事件處理時間不應(yīng)太長丽声,不然影響事件的發(fā)送線程礁蔗,而這個線程可能是UI線程。對應(yīng)的函數(shù)名是onEvent雁社。
MainThread: 事件的處理會在UI線程中執(zhí)行浴井。事件處理時間不能太長,這個不用說的霉撵,長了會ANR的磺浙,對應(yīng)的函數(shù)名是onEventMainThread。
BackgroundThread:事件的處理會在一個后臺線程中執(zhí)行徒坡,對應(yīng)的函數(shù)名是onEventBackgroundThread撕氧,雖然名字是BackgroundThread,事件處理是在后臺線程喇完,但事件處理時間還是不應(yīng)該太長伦泥,因?yàn)槿绻l(fā)送事件的線程是后臺線程,會直接執(zhí)行事件锦溪,如果當(dāng)前線程是UI線程不脯,事件會被加到一個隊(duì)列中,由一個線程依次處理這些事件刻诊,如果某個事件處理時間太長防楷,會阻塞后面的事件的派發(fā)或處理。
Async:事件處理會在單獨(dú)的線程中執(zhí)行则涯,主要用于在后臺線程中執(zhí)行耗時操作复局,每個事件會開啟一個線程(有線程池),但最好限制線程的數(shù)目粟判。
根據(jù)事件訂閱都函數(shù)名稱的不同肖揣,會使用不同的ThreadMode,比如果在后臺線程加載了數(shù)據(jù)想在UI線程顯示浮入,訂閱者只需把函數(shù)命名為onEventMainThread龙优。
簡單使用
基本的使用步驟就是如下4步,點(diǎn)擊此鏈接查看例子及介紹。
定義事件類型:
`public class MyEvent {}`
定義事件處理方法:
`public void onEventMainThread`
注冊訂閱者:
`EventBus.getDefault().register(this)`
發(fā)送事件:
`EventBus.getDefault().post(new MyEvent())`
實(shí)現(xiàn)
**EventBus**使用方法很簡單彤断,但用一個東西野舶,如果不了解它的實(shí)現(xiàn)用起來心里總是沒底,萬一出問題咋辦都不知道宰衙,所以還是研究一下它的實(shí)現(xiàn)平道,肯定要Read the fucking Code。其實(shí)主要是`EventBus`這一個類供炼,在看看Code時需要了解幾個概念與成員一屋,了解了這些后實(shí)現(xiàn)就很好理解了。
EventType:onEvent\*函數(shù)中的參數(shù)袋哼,表示事件的類型
Subscriber:訂閱源冀墨,即調(diào)用register注冊的對象,這個對象內(nèi)包含onEvent\*函數(shù)
SubscribMethod:`Subscriber`內(nèi)某一特定的onEvent\*方法涛贯,內(nèi)部成員包含一個`Method`類型的method成員表示這個onEvent\*方法诽嘉,一個`ThreadMode`成員threadMode表示事件的處理線程,一個`Class`類型的eventType成員表示事件的類型`EventType`弟翘。
Subscription虫腋,表示一個訂閱對象,包含訂閱源`Subscriber`稀余,訂閱源中的某一特定方法`SubscribMethod`悦冀,這個訂閱的優(yōu)先級`priopity`
了解了以上幾個概念后就可以看`EventBus`中的幾個重要成員了
//EventType -> List,事件到訂閱對象之間的映射privatefinalMap, CopyOnWriteArrayList>subscriptionsByEventType;//Subscriber -> List睛琳,訂閱源到它訂閱的的所有事件類型的映射privatefinalMap>>typesBySubscriber;//stickEvent事件盒蟆,后面會看到privatefinalMap, Object>stickyEvents;//EventType -> List,事件到它的父事件列表的映射掸掏。即緩存一個類的所有父類privatestaticfinalMap, List>> eventTypesCache =newHashMap, List>>();
注冊事件:Register
通過`EventBus.getDefault().register`方法可以向`EventBus`注冊來訂閱事件茁影,`register`有很多種重載形式宙帝,但大都被標(biāo)記為`Deprecated`了丧凤,所以還是不用為好,前面說了事件處理方法都是以*onEvent*開頭步脓,其實(shí)是可以通過register方法修改的愿待,但相應(yīng)的方法被廢棄了,還是不要用了靴患,就用默認(rèn)的*onEvent*仍侥,除下廢棄的register方法,還有以下4個**public**的`register`方法
publicvoidregister(Object subscriber) {
register(subscriber, defaultMethodName,false, 0);
}publicvoidregister(Object subscriber,intpriority) {
register(subscriber, defaultMethodName,false, priority);
}publicvoidregisterSticky(Object subscriber) {
register(subscriber, defaultMethodName,true, 0);
}publicvoidregisterSticky(Object subscriber,intpriority) {
register(subscriber, defaultMethodName,true, priority);
}
可以看到鸳君,這4個方法都調(diào)用了同一個方法:
privatesynchronizedvoidregister(Object subscriber, String methodName,booleansticky,intpriority) {
List subscriberMethods =subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
methodName);for(SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
第一個參數(shù)就是訂閱源农渊,第二個參數(shù)就是用到指定方法名約定的,默認(rèn)為*onEvent*開頭或颊,說默認(rèn)是其實(shí)是可以通過參數(shù)修改的砸紊,但前面說了传于,方法已被廢棄,最好不要用醉顽。第三個參數(shù)表示是否是*Sticky Event*沼溜,第4個參數(shù)是優(yōu)先級,這兩個后面再說游添。
在上面這個方法中系草,使用了一個叫`SubscriberMethodFinder`的類,通過其`findSubscriberMethods`方法找到了一個`SubscriberMethod`列表唆涝,前面知道了`SubscriberMethod`表示Subcriber內(nèi)一個onEvent\*方法找都,可以看出來`SubscriberMethodFinder`類的作用是在Subscriber中找到所有以methodName(即默認(rèn)的onEvent)開頭的方法,每個找到的方法被表示為一個`SubscriberMethod`對象石抡。
`SubscriberMethodFinder`就不再分析了檐嚣,但有兩點(diǎn)需要知道:
所有事件處理方法**必需是`public void`類型**的,并且只有一個參數(shù)表示*EventType*啰扛。
`findSubscriberMethods`不只查找*Subscriber*內(nèi)的事件處理方法嚎京,**同時還會查到它的繼承體系中的所有基類中的事件處理方法**。
找到*Subscriber*中的所有事件處理方法后隐解,會對每個找到的方法(表示為`SubscriberMethod`對象)調(diào)用`subscribe`方法注冊鞍帝。`subscribe`方法干了三件事:
根據(jù)`SubscriberMethod`中的*EventType*類型將`Subscribtion`對象存放在`subscriptionsByEventType`中。建立*EventType*到*Subscription*的映射煞茫,每個事件可以有多個訂閱者帕涌。
根據(jù)`Subscriber`將`EventType`存放在`typesBySubscriber`中,建立*Subscriber*到*EventType*的映射续徽,每個Subscriber可以訂閱多個事件蚓曼。
如果是*Sticky*類型的訂閱者,直接向它發(fā)送上個保存的事件(如果有的話)钦扭。
通過*Subscriber*到*EventType*的映射纫版,我們就可以很方便地使一個Subscriber取消接收事件,通過*EventType*到*Sucscribtion*的映射客情,可以方便地將相應(yīng)的事件發(fā)送到它的每一個訂閱者其弊。
Post事件
直接調(diào)用`EventBus.getDefault().post(Event)就可以發(fā)送事件,根據(jù)Event的類型就可以發(fā)送到相應(yīng)事件的訂閱者膀斋。
publicvoidpost(Object event) {
PostingThreadState postingState=currentPostingThreadState.get();
List eventQueue =postingState.eventQueue;
eventQueue.add(event);if(postingState.isPosting) {return;
}else{
postingState.isMainThread= Looper.getMainLooper() ==Looper.myLooper();
postingState.isPosting=true;if(postingState.canceled) {thrownewEventBusException("Internal error. Abort state was not reset");
}try{while(!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
}finally{
postingState.isPosting=false;
postingState.isMainThread=false;
}
}
}
可以看到post內(nèi)使用了`PostingThreadState`的對象梭伐,并且是`ThreadLocal`,來看`PostingThreadState`的定義:
finalstaticclassPostingThreadState {
List eventQueue =newArrayList();booleanisPosting;booleanisMainThread;
Subscription subscription;
Object event;booleancanceled;
}
主要是有個成員`eventQueue`仰担,由于是ThreadLocal糊识,所以結(jié)果就是,每個線程有一個`PostingThreadState`對象,這個對象內(nèi)部有一個事件的隊(duì)列赂苗,并且有一個成員`isPosting`表示現(xiàn)在是否正在派發(fā)事件铃将,當(dāng)發(fā)送事件開始時,會依次取出隊(duì)列中的事件發(fā)送出去哑梳,如果正在派發(fā)事件劲阎,那么post直接把事件加入隊(duì)列后返回,還有個成員`isMainThread`鸠真,這個成員在實(shí)際派發(fā)事件時會用到悯仙,在`postSingleEvent`中會用到。
privatevoidpostSingleEvent(Object event, PostingThreadState postingState)throwsError {
Class eventClass =event.getClass();
List> eventTypes = findEventTypes(eventClass);//1booleansubscriptionFound =false;intcountTypes =eventTypes.size();for(inth = 0; h < countTypes; h++) {//2Class clazz =eventTypes.get(h);
CopyOnWriteArrayListsubscriptions;synchronized(this) {
subscriptions=subscriptionsByEventType.get(clazz);
}if(subscriptions !=null&& !subscriptions.isEmpty()) {//3for(Subscription subscription : subscriptions) {
postingState.event=event;
postingState.subscription=subscription;booleanaborted =false;try{
postToSubscription(subscription, event, postingState.isMainThread);//4aborted =postingState.canceled;
}finally{
postingState.event=null;
postingState.subscription=null;
postingState.canceled=false;
}if(aborted) {break;
}
}
subscriptionFound=true;
}
}if(!subscriptionFound) {
Log.d(TAG,"No subscribers registered for event " +eventClass);if(eventClass != NoSubscriberEvent.class&& eventClass != SubscriberExceptionEvent.class) {
post(newNoSubscriberEvent(this, event));
}
}
}
來看一下`postSingleEvent`這個函數(shù)吠卷,首先看第一點(diǎn)锡垄,調(diào)用了`findEventTypes`這個函數(shù)墨技,代碼不帖了闻坚,這個函數(shù)的應(yīng)用就是,把這個類的類對象徘禁、實(shí)現(xiàn)的接口及父類的類對象存到一個List中返回.
接下來進(jìn)入第二步疾渴,遍歷第一步中得到的List千贯,對List中的每個類對象(即事件類型)執(zhí)行第三步操作,即找到這個事件類型的所有訂閱者向其發(fā)送事件搞坝∩η矗可以看到,**當(dāng)我們Post一個事件時桩撮,這個事件的父事件(事件類的父類的事件)也會被Post敦第,所以如果有個事件訂閱者接收Object類型的事件,那么它就可以接收到所有的事件**店量。
還可以看到芜果,實(shí)際是通過第四步中的`postToSubscription`來發(fā)送事件的,在發(fā)送前把事件及訂閱者存入了`postingState`中融师。再來看`postToSubscription`
privatevoidpostToSubscription(Subscription subscription, Object event,booleanisMainThread) {switch(subscription.subscriberMethod.threadMode) {casePostThread:
invokeSubscriber(subscription, event);break;caseMainThread:if(isMainThread) {
invokeSubscriber(subscription, event);
}else{
mainThreadPoster.enqueue(subscription, event);
}break;caseBackgroundThread:if(isMainThread) {
backgroundPoster.enqueue(subscription, event);
}else{
invokeSubscriber(subscription, event);
}break;caseAsync:
asyncPoster.enqueue(subscription, event);break;default:thrownewIllegalStateException("Unknown thread mode: " +subscription.subscriberMethod.threadMode);
}
}
這里就用到`ThreadMode`了:
如果是PostThread右钾,直接執(zhí)行
如果是MainThread,判斷當(dāng)前線程诬滩,如果本來就是UI線程就直接執(zhí)行霹粥,否則加入`mainThreadPoster`隊(duì)列
如果是后臺線程灭将,如果當(dāng)前是UI線程疼鸟,加入`backgroundPoster`隊(duì)列,否則直接執(zhí)行
如果是Async庙曙,加入`asyncPoster`隊(duì)列
BackgroundPoster
privatefinalPendingPostQueue queue;publicvoidenqueue(Subscription subscription, Object event) {
PendingPost pendingPost=PendingPost.obtainPendingPost(subscription, event);synchronized(this) {
queue.enqueue(pendingPost);if(!executorRunning) {
executorRunning=true;
EventBus.executorService.execute(this);
}
}
}
代碼比較簡單空镜,其實(shí)就是,待發(fā)送的事件被封裝成了`PendingPost`對象,`PendingPostQueue`是一個`PendingPost`對象的隊(duì)列吴攒,當(dāng)`enqueue`時就把這個事件放到隊(duì)列中张抄,`BackgroundPoster`其實(shí)就是一個Runnable對象,當(dāng)`enqueue`時洼怔,如果這個Runnable對象當(dāng)前沒被執(zhí)行署惯,就將`BackgroundPoster`加入EventBus中的一個線程池中,當(dāng)`BackgroundPoster`被執(zhí)行時镣隶,會依次取出隊(duì)列中的事件進(jìn)行派發(fā)极谊。當(dāng)長時間無事件時`BackgroundPoster`所屬的線程被會銷毀,下次再Post事件時再創(chuàng)建新的線程安岂。
HandlerPoster
`mainThreadPoster`是一個`HandlerPoster`對象轻猖,`HandlerPoster`繼承自`Handler`,構(gòu)造函數(shù)中接收一個`Looper`對象域那,當(dāng)向`HandlerPoster` enqueue事件時咙边,會像`BackgroundPoster`一樣把這個事件加入隊(duì)列中, 只是如果當(dāng)前沒在派發(fā)消息就向自身發(fā)送Message
voidenqueue(Subscription subscription, Object event) {
PendingPost pendingPost=PendingPost.obtainPendingPost(subscription, event);synchronized(this) {
queue.enqueue(pendingPost);if(!handlerActive) {
handlerActive=true;if(!sendMessage(obtainMessage())) {thrownewEventBusException("Could not send handler message");
}
}
}
}
在`handleMessage`中會依次取出隊(duì)列中的消息交由`EventBus`直接調(diào)用事件處理函數(shù)次员,而`handleMessage`執(zhí)行所在的線程就是構(gòu)造函數(shù)中傳進(jìn)來的`Looper`所屬的線程败许,在`EventBus`中構(gòu)造`mainThreadPoster`時傳進(jìn)來的是MainLooper,所以會在UI線程中執(zhí)行淑蔚。
AsyncPoster
`AsyncPoster`就簡單了檐束,把每個事件都加入線程池中處理
publicvoidenqueue(Subscription subscription, Object event) {
PendingPost pendingPost=PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
EventBus.executorService.execute(this);
}
Stick Event
通過`registerSticky`可以注冊Stick事件處理函數(shù),前面我們知道了束倍,無論是`register`還是`registerSticky`最后都會調(diào)用`Subscribe`函數(shù)被丧,在`Subscribe`中有這么一段代碼:
也就是會根據(jù)事件類型從`stickyEvents`中查找是否有對應(yīng)的事件,如果有绪妹,直接發(fā)送這個事件到這個訂閱者甥桂。而這個事件是什么時候存起來的呢,同`register`與`registerSticky`一樣邮旷,和`post`一起的還有一個`postSticky`函數(shù):
if(sticky) {
Object stickyEvent;synchronized(stickyEvents) {
stickyEvent=stickyEvents.get(eventType);
}if(stickyEvent !=null) {//If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)//--> Strange corner case, which we don't take care of here.postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() ==Looper.myLooper());
}
}
當(dāng)通過`postSticky`發(fā)送一個事件時黄选,這個類型的事件的最后一次事件會被緩存起來,當(dāng)有訂閱者通過`registerSticky`注冊時婶肩,會把之前緩存起來的這個事件直接發(fā)送給它办陷。
事件優(yōu)先級Priority
`register`的函數(shù)重載中有一個可以指定訂閱者的優(yōu)先級,我們知道`EventBus`中有一個事件類型到List的映射律歼,在這個映射中民镜,所有的Subscription是按priority排序的,這樣當(dāng)post事件時险毁,優(yōu)先級高的會先得到機(jī)會處理事件制圈。
優(yōu)先級的一個應(yīng)用就事们童,高優(yōu)先級的事件處理函數(shù)可以終于事件的傳遞,通過`cancelEventDelivery`方法鲸鹦,但有一點(diǎn)需要注意慧库,`這個事件的ThreadMode必須是PostThread`,并且只能終于它在處理的事件馋嗜。
# 缺點(diǎn)
無法進(jìn)程間通信齐板,如果一個應(yīng)用內(nèi)有多個進(jìn)程的話就沒辦法了
# 注意事項(xiàng)及要點(diǎn)
同一個onEvent函數(shù)不能被注冊兩次,所以不能在一個類中注冊同時還在父類中注冊
當(dāng)Post一個事件時葛菇,這個事件類的父類的事件也會被Post覆积。
Post的事件無Subscriber處理時會Post `NoSubscriberEvent`事件,當(dāng)調(diào)用Subscriber失敗時會Post `SubscriberExceptionEvent`事件熟呛。
其他
`EventBus`中還有個Util包宽档,主要作用是可以通過`AsyncExecutor`執(zhí)行一個Runnable,通過內(nèi)部的RunnableEx(可以搜索異常的Runnable)當(dāng)Runnable拋出異常時通過`EventBus`發(fā)消息顯示錯誤對話框庵朝。沒太大興趣吗冤,不作分析
項(xiàng)目主頁:https://github.com/greenrobot/EventBus
一個很簡單的Demo,Activity中包含列表和詳情兩個Fragment九府,Activity啟動時加載一個列表椎瘟,點(diǎn)擊列表后更新詳情數(shù)據(jù):EventBusDemo