記一個Otto Bus使用上的坑

緣起

今天晚上有個同事找我看一個問題,因?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事件的嵌套。可能還有更好的解決方式,歡迎交流、指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評論 25 709
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,316評論 0 17
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,228評論 0 1
  • 它好像愛上她了。 那個炎熱的夏天,空氣中都浮動著燥熱,它趴在那顆老槐樹上,恪盡職守地叫著。突然,它看到了她,那么美...
    鯨衿閱讀 216評論 0 0

友情鏈接更多精彩內(nèi)容