記一個Otto Bus使用上的坑

arls5614 8年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

緣起

今天晚上有個同事找我看一個問題,因為他們用到了我們的模塊,而我們模塊會在工作結束時調用他們塞進來的callback返回回去,但是在他們的callback中兩段基本相同的代碼卻有著不一樣的行為,很是令人費解。類似下面這樣:

以下是callback中的偽代碼:

case 1: // 不同的case,執行的邏輯是相同的 // before notify code notifyResult(case 1); // 這里面有bus.postEvent(Intent)的調用 // after notify code break; case 2: // before notify code notifyResult(case 2); // 這里面有bus.postEvent(Intent)的調用 // after notify code break;

另外的某個Act中有handler方法,如下: @subscribe public void eventConsumeMethod(Intent intent) { System.out.println("consumed"); }</code></pre>

一般大家都會覺得這2種沒什么差別,輸出(執行順序)都應該是:

before -> consumed - > after

在我們這里的case2確實是這樣,但case1的輸出卻是:

before -> after -> consumed

我當時看到的時候也覺得很不可思議,因為Bus的代碼我曾經認真看過,按我的理解post event肯定會同步執行的,即post event緊接著就會進到handler方法中,所以這里consume肯定是接著before的啊。下面讓我們來分析下出現這個神奇現象的原因。

源碼&單步

在分析之前,再補充說明下,前面代碼中的case1是通過我們模塊里的post Event調出去的,而case2是直接正常回調出去的。

接下來,當我單步調試的時候,很自然地來到了 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(); }</code></pre>

當出現上面case1的情況時,我就在想會不會是在post的過程中有某些 return 導致提前返回了,所以在看代碼的時候,我專門留意了下,這個方法看起來沒有我想要找的 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; // 罪魁禍首就是這貨!!!
    }

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);
}</code></pre> 

一進來的if和注釋算是給了我們答案,我單步debug的時候也發現確實是在此處提前return了,即這次事件并沒有馬上被處理。這里的注釋翻譯下就是說:

如果我們正在分發事件,則不繼續分發又出現的事件,因為那樣會導致事件重入和亂序,所以我們會在處理完當前的事件后再回過頭來處理新發生的事件。這里的 isDispatching ,是個ThreadLocal<Boolean>類型,和每個線程關聯。這段代碼和注釋對應到我們前面出問題的case1中就是:

在我們代碼中是通過處理A事件調到上面的callback的(即正在分發處理事件A),而case1中又post了一個新的事件B,so按照這段源碼的意思,在處理A事件的過程中,B不會被處理,而是等A處理完后,才會回過來接著處理B,注意理解上面源碼中的 while(true) 循環。

總結

一般來說,即使發生了case1的情況也不是啥大問題,但很不巧的是,這位同事的代碼剛好就需要先執行consume方法,然后再執行after邏輯,否則就不對。所以,通過上面的分析,我們也看到了, 使用Otto Bus最好不要在處理某個事件的過程中又post了另一個事件,因為越復雜的case,可能會產生越出乎你意料之外的行為,有時也可能會困擾你 。

當然了,如果全是自己控制,那很好辦,大家很容易能避開這樣的寫法,但就像我們這里一樣,一個大的app經常是需要各個模塊配合工作的,別人調用你的方法,你不大可能知道他是以怎樣的形式回調你的,所以想避免還是不那么明顯的。針對這個問題,可以很簡單的用 Handler.postRunnable 來解決,避開post事件的嵌套。可能還有更好的解決方式,歡迎交流、指正。

 

來自:http://www.jianshu.com/p/67ccce928edb

 

 本文由用戶 arls5614 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!