緣起
今天晚上有個同事找我看一個問題,因?yàn)樗麄冇玫搅宋覀兊哪K,而我們模塊會在工作結(jié)束時調(diào)用他們?nèi)M(jìn)來的callback返回回去,但是在他們的callback中兩段基本相同的代碼卻有著不一樣的行為,很是令人費(fèi)解。類似下面這樣:
以下是callback中的偽代碼:
case 1: // 不同的case,執(zhí)行的邏輯是相同的
// before notify code
notifyResult(case 1); // 這里面有bus.postEvent(Intent)的調(diào)用
// after notify code
break;
case 2:
// before notify code
notifyResult(case 2); // 這里面有bus.postEvent(Intent)的調(diào)用
// after notify code
break;
另外的某個Act中有handler方法,如下:
@subscribe
public void eventConsumeMethod(Intent intent) {
System.out.println("consumed");
}
一般大家都會覺得這2種沒什么差別,輸出(執(zhí)行順序)都應(yīng)該是:
before -> consumed - > after
在這里的case2確實(shí)是這樣,但case1的輸出卻是:
before -> after -> consumed
我當(dāng)時看到的時候也覺得很不可思議,因?yàn)锽us的代碼我曾經(jīng)認(rèn)真看過,按我的理解post event肯定會同步執(zhí)行的,即post event緊接著就會進(jìn)到handler方法中,所以這里consume肯定是接著before的啊。下面讓我們來分析下出現(xiàn)這個神奇現(xiàn)象的原因。
源碼&單步
在分析之前,再補(bǔ)充說明下,前面代碼中的case1是通過我們模塊里的post Event調(diào)出去的,而case2是直接正?;卣{(diào)出去的。
接下來,當(dāng)我單步調(diào)試的時候,很自然地來到了Bus.post(Object event)方法,其源碼如下:
public void post(Object event) {
if (event == null) {
throw new NullPointerException("Event to post must not be null.");
}
enforcer.enforce(this);
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
Set<EventHandler> wrappers = getHandlersForEventType(eventType);
if (wrappers != null && !wrappers.isEmpty()) {
dispatched = true;
for (EventHandler wrapper : wrappers) {
enqueueEvent(event, wrapper);
}
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
當(dāng)出現(xiàn)上面case1的情況時,我就在想會不會是在post的過程中有某些return導(dǎo)致提前返回了,所以在看代碼的時候,我專門留意了下,這個方法看起來沒有我想要找的return,最后我們來到了dispatchQueuedEvents方法,接著往下看,其源碼如下:
protected void dispatchQueuedEvents() {
// don't dispatch if we're already dispatching, that would allow reentrancy and out-of-order events. Instead, leave
// the events to be dispatched after the in-progress dispatch is complete.
if (isDispatching.get()) {
return; // 罪魁禍?zhǔn)拙褪沁@貨?。?!
}
isDispatching.set(true);
try {
while (true) {
EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
if (eventWithHandler == null) {
break;
}
if (eventWithHandler.handler.isValid()) {
dispatch(eventWithHandler.event, eventWithHandler.handler);
}
}
} finally {
isDispatching.set(false);
}
一進(jìn)來的if和注釋算是給了我們答案,我單步debug的時候也發(fā)現(xiàn)確實(shí)是在此處提前return了,即這次事件并沒有馬上被處理。這里的注釋翻譯下就是說:
如果我們正在分發(fā)事件,則不繼續(xù)分發(fā)又出現(xiàn)的事件,因?yàn)槟菢訒?dǎo)致事件重入和亂序,所以我們會在處理完當(dāng)前的事件后再回過頭來處理新發(fā)生的事件。這里的isDispatching,是個ThreadLocal<Boolean>類型,和每個線程關(guān)聯(lián)。這段代碼和注釋對應(yīng)到我們前面出問題的case1中就是:
在我們代碼中是通過處理A事件調(diào)到上面的callback的(即正在分發(fā)處理事件A),而case1中又post了一個新的事件B,so按照這段源碼的意思,在處理A事件的過程中,B不會被處理,而是等A處理完后,才會回過來接著處理B,注意理解上面源碼中的while(true)循環(huán)。
總結(jié)
一般來說,即使發(fā)生了case1的情況也不是啥大問題,但很不巧的是,這位同事的代碼剛好就需要先執(zhí)行consume方法,然后再執(zhí)行after邏輯,否則就不對。所以,通過上面的分析,我們也看到了,使用Otto Bus最好不要在處理某個事件的過程中又post了另一個事件,因?yàn)樵綇?fù)雜的case,可能會產(chǎn)生越出乎你意料之外的行為,有時也可能會困擾你。
當(dāng)然了,如果全是自己控制,那很好辦,大家很容易能避開這樣的寫法,但就像我們這里一樣,一個大的app經(jīng)常是需要各個模塊配合工作的,別人調(diào)用你的方法,你不大可能知道他是以怎樣的形式回調(diào)你的,所以想避免還是不那么明顯的。針對這個問題,可以很簡單的用Handler.postRunnable來解決,避開post事件的嵌套。可能還有更好的解決方式,歡迎交流、指正。