Android知识Android开发Android技术知识

记一个Otto Bus使用上的坑

2016-11-23  本文已影响300人  tmp_zhao

缘起

今天晚上有个同事找我看一个问题,因为他们用到了我们的模块,而我们模块会在工作结束时调用他们塞进来的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");
}

一般大家都会觉得这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();
}

当出现上面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);
    }
  

一进来的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事件的嵌套。可能还有更好的解决方式,欢迎交流、指正。

上一篇下一篇

猜你喜欢

热点阅读