EventBus原理剖析

EventBus

EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递。传统的事件传递方式包括:Handler、BroadCastReceiver、Interface 回调,相比之下 EventBus 的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦。

下列源码基于EventBus3.0.0进行分析

Subscribe

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
  //处理事件线程
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
  //是否粘性,详见postSticky
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
  //优先级
    int priority() default 0;
}

Register

public void register(Object subscriber) {
  //获取类中使用@Subscribe字段的方法列表
  //既然不是APT,不过也能理解,这部分是动态去注册的
  Class<?> subscriberClass = subscriber.getClass();
  List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
  synchronized (this) {
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
      //添加到数组缓存中
      subscribe(subscriber, subscriberMethod);
    }
  }
}
//findSubscriberMethods
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
  //METHOD_CACHE缓存,防止重新找,提高性能
  List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
  if (subscriberMethods != null) {
    return subscriberMethods;
  }
  //遍历方法和注解得到注册了方法列表
  //通过Class做到其所有的方法,再找到其使用了Subscribe注解的方法
  if (ignoreGeneratedIndex) {
    subscriberMethods = findUsingReflection(subscriberClass);
  } else {
    subscriberMethods = findUsingInfo(subscriberClass);
  }
  if (subscriberMethods.isEmpty()) {
    //如注册了,没有消息处理的话会直接报错
    throw new EventBusException("Subscriber " + subscriberClass
                                + " and its super classes have no public methods with the @Subscribe annotation");
  } else {
    METHOD_CACHE.put(subscriberClass, subscriberMethods);
    return subscriberMethods;
  }
}

Post

public void post(Object event) {
  //获取当前线程,将message添加到消息队列中
  PostingThreadState postingState = currentPostingThreadState.get();
  List<Object> eventQueue = postingState.eventQueue;
  eventQueue.add(event);

  //串行处理事件
  if (!postingState.isPosting) {
    //发送事件线程是否是主线程
    postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
    postingState.isPosting = true;
    if (postingState.canceled) {
      throw new EventBusException("Internal error. Abort state was not reset");
    }
    try {
      //事件队列不为空就一直处理
      while (!eventQueue.isEmpty()) {
        //单条事件,实际处理,如此时在主线程处理耗时操作,会阻塞
        postSingleEvent(eventQueue.remove(0), postingState);
      }
    } finally {
      postingState.isPosting = false;
      postingState.isMainThread = false;
    }
  }
}

postSingleEvent

拆分事件类型(包括事件、事件父类、实现接口)

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
  Class<?> eventClass = event.getClass();
  boolean subscriptionFound = false;
  //事件是否继承,默认为true,也就是说会发送一个事件及其父类和接口的所以事件类
  if (eventInheritance) {
    //找到Event以及Event的父类和其所实现的接口
    List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
    int countTypes = eventTypes.size();
    for (int h = 0; h < countTypes; h++) {
      Class<?> clazz = eventTypes.get(h);
      subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
    }
  } else {
    subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
  }
  if (!subscriptionFound) {
    if (logNoSubscriberMessages) {
      Log.d(TAG, "No subscribers registered for event " + eventClass);
    }
    //如果发送一个没有被注册过的事件,会默认发送一个NoSubscriberEvent
    if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
        eventClass != SubscriberExceptionEvent.class) {
      post(new NoSubscriberEvent(this, event));
    }
  }
}

postSingleEventForEventType

找到具体的消费类,进行事件消费

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
  //找到注册此event的事件类列表,register时候将其放进去
  CopyOnWriteArrayList<Subscription> subscriptions;
  synchronized (this) {
    subscriptions = subscriptionsByEventType.get(eventClass);
  }
  if (subscriptions != null && !subscriptions.isEmpty()) {
    for (Subscription subscription : subscriptions) {
      postingState.event = event;
      postingState.subscription = subscription;
      boolean aborted = false;
      try {
        //找到具体的消费类,进行事件消费
        postToSubscription(subscription, event, postingState.isMainThread);
        //高优先级的处理完了,可以选择终止此次事件的继续分发
        aborted = postingState.canceled;
      } finally {
        postingState.event = null;
        postingState.subscription = null;
        postingState.canceled = false;
      }
      if (aborted) {
        break;
      }
    }
    return true;
  }
  return false;
}

postToSubscription

消费事件

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
  switch (subscription.subscriberMethod.threadMode) {
    case POSTING:
      //发送线程直接消费
      invokeSubscriber(subscription, event);
      break;
    case MAIN:
      if (isMainThread) {
      //在主线程直接消费
        invokeSubscriber(subscription, event);
      } else {
        //不在主线程发送,则通过Handler继续消费
        mainThreadPoster.enqueue(subscription, event);
      }
      break;
    case BACKGROUND:
      //后台线程,会处理一个事件队列
      if (isMainThread) {
        backgroundPoster.enqueue(subscription, event);
      } else {
        invokeSubscriber(subscription, event);
      }
      break;
    case ASYNC:
      //异步线程进行消费,只处理此事件
      asyncPoster.enqueue(subscription, event);
      break;
    default:
      throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
  }
}

对ThreadMode源码分析可知:

  1. PostThread`:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作
  2. MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作
  3. BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
  4. Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问

postSticky

发送粘性事件

public void postSticky(Object event) {
  synchronized (stickyEvents) {
    //将事件缓存到stickyEvents中,当再次register时候,会去stickyEvents中获取是否存在,如已经缓存则直接消费
    //使用场景:如果您正在跟踪用户的位置,或简单的缓存数据,跟踪电池电量等,您可以使用粘性事件。
    stickyEvents.put(event.getClass(), event);
  }
  // Should be posted after it is putted, in case the subscriber wants to remove immediately
  post(event);
}
//register中调用subscribe
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
  ...略
  if (subscriberMethod.sticky) {
    if (eventInheritance) {
      Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
      for (Map.Entry<Class<?>, Object> entry : entries) {
        Class<?> candidateEventType = entry.getKey();
        if (eventType.isAssignableFrom(candidateEventType)) {
          Object stickyEvent = entry.getValue();
          checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
      }
    } else {
      Object stickyEvent = stickyEvents.get(eventType);
      checkPostStickyEventToSubscription(newSubscription, stickyEvent);
    }
  }
}

Priority

优先级的应用:高优先级的事件处理函数可以终止事件的传递,通过cancelEventDelivery方法,但有一点需要注意,这个事件的ThreadMode必须是PostThread,并且只能终于它在处理的事件。

 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
   //...略
     int size = subscriptions.size();
   for (int i = 0; i <= size; i++) {
     //优先级高的会在队列的前面,有优先执行权
     if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
       subscriptions.add(i, newSubscription);
       break;
     }
   }
   //...略
 }

cancelEventDelivery

终止事件分发

public void cancelEventDelivery(Object event) {
  PostingThreadState postingState = currentPostingThreadState.get();
  
  if (!postingState.isPosting) {//事件必须在发送中
    throw new EventBusException(
      "This method may only be called from inside event handling methods on the posting thread");
  } else if (event == null) {//事件必须非null
    throw new EventBusException("Event may not be null");
  } else if (postingState.event != event) {//事件必须是当前正在处理
    throw new EventBusException("Only the currently handled event may be aborted");
  } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
    //事件必须在发送线程中处理
    throw new EventBusException(" event handlers may only abort the incoming event");
  }
  //canceld标示置为true,postSingleEventForEventType中进行中断
  postingState.canceled = true;
}

注意事项

  1. 如消费事件的线程是在主线程,不宜做耗时操作
  2. post的事件会连着其父类和所实现的接口一起分发
  3. register&unregister一定要配对出现,否则会引发内存泄露
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 24,925评论 23 184
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,475评论 0 50
  • 缘由: 平时工作,因为懒于动笔的原因,也没注重技术和经验的积累,导致之前曾经研究过的问题现在又忘记了,所以要慢慢注...
    斜杠时光阅读 433评论 0 0
  • 图片拍摄于西安壶口瀑布 风露出狰狞面目,在身体里忙碌穿梭,我独自在路上,竖起衣领,望望前面拉下帷幕的苍穹,加快了脚...
    是是而非阅读 373评论 0 0
  • 5月17日是深圳威豹押运成立二十周年的纪念日,威豹押运当天向全媒体做了开放,当代押运行业的神秘面纱得以在大众面前揭...
    五色未央阅读 1,863评论 0 1