EventBus深入理解记录

本片文章不写简单实用,如果是想学习集成EventBus,本篇文章不适合你。

目录

  • 图解EventBus类的主要依赖
  • 类EventBus(含事件多线程工作原理)
  • 类SubscriberMethod
  • 类Subscription
  • 类SubscriberMethodFinder

图解EventBus类的主要依赖

EventBus类依赖图.jpg

类EventBus

register方法:

(1)将订阅者的每个订阅方法缓存在SubscriberMethodFinder中,避免重复订阅
(2)将每个订阅方法缓存在HasMap<消息体, CopyOnWriteArrayList<Subscription>>中,用来记录所有拥有‘消息体’的方法,在发送消息时,根据’消息体’找到所有方法进行触发
(3)将订阅者缓存在HashMap<订阅者, List<消息体>>中,用来对外判断订阅者是否订阅某方法
(4)如果订阅方法是粘性广播,如果之前已有发送该事件,那么先触发一次该订阅方法。
原理是:

 public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        post(event);
 }

每次发送粘性广播,使用HashMap将<订阅方法的参数的类名,订阅方法的参>缓存起来。在订阅方法时,发现参数之前已有发送过消息,那么就取到对应参数,触发该订阅方法,因此这里有以下限制:
(1)HashMap保存的是参数的类名,因此,要使用粘性事件,每个事件必须得定义一个特殊的参数来避免消息错乱
unregister方法:
(1)清空HasMap<消息体, CopyOnWriteArrayList<Subscription>>
(2)清空HashMap<订阅者, List<消息体>>

post方法(含事件多线程工作原理)

事件多线程工作原理

如下面代码所示,currentPostingThreadState使用ThreadLocal封装PostingThreadState,这样保证每个发送线程有自己的PostingThreadState,也就是每个线程有自己消息队列以及状态,在调用post(Object event)方法不相互影响。倘若不使用ThreadLocal,只有一个消息队列,无法记录维护消息的发送线程,线程切换处理将会很复杂,例如,当ThreadMode为默认的POSTING(消息发送线程是什么类型,订阅的方法接收时就运行在什么类型的线程)时,多线程情况下,这时非常可能出现,在主线程分发的消息,订阅的方法会运行在异步线程中。

public void post(Object event) {
  //currentPostingThreadState.get使用ThreadLocal,这样保证每个发送线程有自己的PostingThreadState,
  //也就是每个线程有自己消息队列以及状态,在调用post(Object event)方法不相互影响。
      //倘若不使用ThreadLocal,只有一个消息队列,无法记录维护消息的发送线程,线程切换处理将会很复杂,
      //例如,当ThreadMode为默认的POSTING(消息发送线程是什么类型,订阅的方法接收时就运行在什么类型的线程)时,
      //多线程情况下,这时非常可能出现,在主线程分发的消息,订阅的方法会运行在异步线程中
      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;
          }
      }
  }

postSticky方法:

postSticky(消息体)与post(消息体)区别在于,会先将‘消息体’使用ConcurrentHashMap<消息体Class, 消息体>缓存起来,这个缓存在下一个订阅者订阅时,如果有订阅方法的消息体对得上,会先触发一下。因此达到了粘性广播的目的,如下所示。

//代码位于EventBus#postSticky(Object event)中
public void postSticky(Object event) {
        synchronized (stickyEvents) {
            // stickyEvents就是ConcurrentHashMap<消息体Class, 消息体>
            stickyEvents.put(event.getClass(), event);
        }
        post(event);
 }

看看订阅时触发的代码:

//代码位于EventBus#subscribe(Object subscriber, SubscriberMethod subscriberMethod)中
    if (subscriberMethod.sticky) {
        //eventInheritance,默认true。true表示如果订阅的方法的参数是 
       //stickyEvents(ConcurrentHashMap<消息体Class, 消息体>)
      //中某个key‘的父类或者父接口,也要触发。
      //比如,stickyEvents中有key为‘A’的数据,而IA是A的接口,
      //那么在订阅粘性方法method(IA)时,该方法会被触发。
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                //这里有性能问题,建议sticky事件的参数尽量简洁,以下是参数反例:List<Class>)
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                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);
            }
        }

类SubscriberMethod

每个订阅方法就是一个SubscriberMethod,SubscriberMethod包含以下属性

  • Method:Java原有类
  • ThreadMode:4种类型,默认、主线程、后台线程(内置的单个线程中执行)、异步线程(每次起一个线程)
  • final Class<?> eventType:订阅方法的参数,被订阅的方法肯定只有一个参数
  • priority:优先级,在队列中,在订阅时,将SubscriberMethod按照priority从大到小排序
  • sticky:是否是粘性广播
  • String methodString:方便在两个SubscriberMethod的比较,规则 Method所属类名#Method名(eventType 名

类Subscription

有以下3个属性

//订阅者
final Object subscriber;
//见上面类SubscriberMethod
    final SubscriberMethod subscriberMethod;
//是否激活,true表示SubscriberMethod还可接受消息。在构造函数中被初始化为true,所以在注册时,遍历查找哪些方法是SubscriberMethod时被注册;在取消注册时这个变量立即被置为false
    volatile boolean active;

类SubscriberMethodFinder

订阅者注册时,用来辅助查找订阅者中是否含有被'@Subscribe’注解的方法,找到的话使用ConcurrentHashMap<Class<?>, List<SubscriberMethod>>缓存起来,起到缓存查询作用

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        if (ignoreGeneratedIndex) {
            //通过反射查找被@Subscribe注解的方法,并且会循环遍历父类查找
            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;
        }
   }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 8,456评论 0 6
  • 我每周会写一篇源代码分析的文章,以后也可能会有其他主题.如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达s...
    SkyKai阅读 25,110评论 23 184
  • 前面对EventBus 的简单实用写了一篇,相信大家都会使用,如果使用的还不熟,或者不够6,可以花2分钟瞄一眼:h...
    gogoingmonkey阅读 2,578评论 0 0
  • 最近在项目中使用了EventBus(3.0),觉得非常好用,于是就看了一些关于EventBus源码分析的文章,现在...
    shenhuniurou阅读 5,397评论 0 4
  • 伏在电脑前写字,发邮件,突然想到好久没有亲笔写一封信了。真的进入数字化年代了,一切都变得那么飞快,快的让人觉得不可...
    弯月流光阅读 4,294评论 0 0

友情链接更多精彩内容