记一个Otto Bus使用上的坑
arls5614
8年前
<h3><strong>缘起</strong></h3> <p>今天晚上有个同事找我看一个问题,因为他们用到了我们的模块,而我们模块会在工作结束时调用他们塞进来的callback返回回去,但是在他们的callback中两段基本相同的代码却有着不一样的行为,很是令人费解。类似下面这样:</p> <pre> <code class="language-java">以下是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> <p>一般大家都会觉得这2种没什么差别,输出(执行顺序)都应该是:</p> <p>before -> consumed - > after</p> <p>在我们这里的case2确实是这样,但case1的输出却是:</p> <p>before -> after -> consumed</p> <p>我当时看到的时候也觉得很不可思议,因为Bus的代码我曾经认真看过,按我的理解post event肯定会同步执行的,即post event紧接着就会进到handler方法中,所以这里consume肯定是接着before的啊。下面让我们来分析下出现这个神奇现象的原因。</p> <h3><strong>源码&单步</strong></h3> <p>在分析之前,再补充说明下,前面代码中的case1是通过我们模块里的post Event调出去的,而case2是直接正常回调出去的。</p> <p>接下来,当我单步调试的时候,很自然地来到了 Bus.post(Object event) 方法,其源码如下:</p> <pre> <code class="language-java">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> <p>当出现上面case1的情况时,我就在想会不会是在post的过程中有某些 return 导致提前返回了,所以在看代码的时候,我专门留意了下,这个方法看起来没有我想要找的 return ,最后我们来到了 dispatchQueuedEvents 方法,接着往下看,其源码如下:</p> <pre> <code class="language-java">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> <p>一进来的if和注释算是给了我们答案,我单步debug的时候也发现确实是在此处提前return了,即这次事件并没有马上被处理。这里的注释翻译下就是说:</p> <p>如果我们正在分发事件,则不继续分发又出现的事件,因为那样会导致事件重入和乱序,所以我们会在处理完当前的事件后再回过头来处理新发生的事件。这里的 isDispatching ,是个ThreadLocal<Boolean>类型,和每个线程关联。这段代码和注释对应到我们前面出问题的case1中就是:</p> <p>在我们代码中是通过处理A事件调到上面的callback的(即正在分发处理事件A),而case1中又post了一个新的事件B,so按照这段源码的意思,在处理A事件的过程中,B不会被处理,而是等A处理完后,才会回过来接着处理B,注意理解上面源码中的 while(true) 循环。</p> <h3><strong>总结</strong></h3> <p>一般来说,即使发生了case1的情况也不是啥大问题,但很不巧的是,这位同事的代码刚好就需要先执行consume方法,然后再执行after逻辑,否则就不对。所以,通过上面的分析,我们也看到了, 使用Otto Bus最好不要在处理某个事件的过程中又post了另一个事件,因为越复杂的case,可能会产生越出乎你意料之外的行为,有时也可能会困扰你 。</p> <p>当然了,如果全是自己控制,那很好办,大家很容易能避开这样的写法,但就像我们这里一样,一个大的app经常是需要各个模块配合工作的,别人调用你的方法,你不大可能知道他是以怎样的形式回调你的,所以想避免还是不那么明显的。针对这个问题,可以很简单的用 Handler.postRunnable 来解决,避开post事件的嵌套。可能还有更好的解决方式,欢迎交流、指正。</p> <p> </p> <p>来自:http://www.jianshu.com/p/67ccce928edb</p> <p> </p>