EventBus系列『一』——注册与注销

简介

本周我将开启一个系列篇,围绕着EventBus的 运行流程 + 源码 的形式进行精细剖析,分为一下几篇

EventBus系列『一』——注册与注销

EventBus系列『二』——Post与postSticky事件的发布与接收

EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想

EventBus准备提要

在讲解我们注册和注销之前EventBus需要做一些准备工作

[1] 将一些特殊重要属性封装成 ,PostingThreadState类型,并将PostingThreadState 的实例放置于ThreadLocal
   final static class PostingThreadState {
        final List eventQueue = new ArrayList<>(); //订阅事件队列
        boolean isPosting; //是否已发布
        boolean isMainThread; //是否在主线程中发布的
        Subscription subscription; //订阅信息
        Object event; //订阅事件 
        boolean canceled; //是否已取消
    }

[2] 准备一个Map集合将订阅事件参数类与事件参数的所有父类集合关联起来
private static final Map, List>> eventTypesCache = new HashMap<>();
[3]创建一个EventBus实例,初始化里面的参数
//创建单例
public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
 public EventBus() {
        this(DEFAULT_BUILDER);
    }
EventBus(EventBusBuilder builder) {
    //创建EventBus的Log打印
    logger = builder.getLogger();  
    //用于将订阅方法的EventType属性与订阅事件集合
    //(CopyOnWriteArrayList)关联,缓存进HashMap
    subscriptionsByEventType = new HashMap<>();  
   ////用于将注册类与注册类中的订阅方法的类型集合(List>)关联,缓存进HashMap
    typesBySubscriber = new HashMap<>(); 
     // //用于缓存粘性事件,将粘性事件的消息实体类 与 粘性事件关联 存入 ConcurrentHashMap中
     stickyEvents = new ConcurrentHashMap<>();
     //创建EventBus主线程实例
    mainThreadSupport = builder.getMainThreadSupport();  
    //创建主线程Poster,用于将事件放入主线程处理队列
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; 
    //创建 后台的Poster ,用于将事件放入后台处理队列
    backgroundPoster = new BackgroundPoster(this); 
     //创建异步Poster  ,用于将事件放入异步处理队列
    asyncPoster = new AsyncPoster(this); 
     //获取订阅事件注册类的索引数量
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    //创建用于查询订阅事件的实例
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, 
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = 
    builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    //获取线程池实例
    executorService = builder.executorService; 
}

EventBus注册

我们先来看一下EventBus注册流程图:

EventBus系列『一』——注册与注销_第1张图片
EventBus注册流程图.jpg

我将历程图分为两个部分 第一部分为【主流程】而 第二部分为【事件集合的遍历流程】 以方便大家理解,那么我们接下来就依据流程图为大家逐一讲解.

[1] EventBus.getDefault() 获取EventBus实例

[2] 进入 EventBus.java #方法 EventBus.getDefault().register(this)函数

public void register(Object subscriber) {
    Class subscriberClass = subscriber.getClass();  //获取 subscriber类对象  即 上层参数  [ this ] 的类对象
     // [ 2.1 ] 检索出注册类中的所有​ [ 订阅信息方法]
    List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    //由于订阅事件必须在同步块中实现,所以这里使用synchroniezd关键字锁住​​
    synchronized (this) {
       //使用for循环,将事件逐个发布​
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // [2.2] 发布订阅信息
            subscribe(subscriber, subscriberMethod);
        }
    }
}​
  • 检索出注册类中的所有​ [ 订阅信息方法]
  • 遍历获取的订阅信息集合,并逐一发布订阅信息
[2.1] 检索注册类中的所有订阅方法 进入 SubscriberMethodFinder.java 执行

subscriberMethodFinder.findSubscriberMethods(subscriberClass)

//全局 [ 订阅事件 ] 方法缓存Map
​private static final Map, List> METHOD_CACHE = new ConcurrentHashMap<>();
​List findSubscriberMethods(Class subscriberClass) {
    //根据注册类class,从缓存中获取订阅事件链​
    List subscriberMethods = METHOD_CACHE.get(subscriberClass);
​    //已缓存过,直接返回订阅事件List
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //​是否使用通过反射生成的索引,默认false
    if (ignoreGeneratedIndex) { 
         //通过索引类取出注册类的订阅事件信息  @问题:注册时如何通过索引类获取注册类的订阅事件集合
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {​
​        //使用反射获取订阅事件链List​    @问题:注册时如何通过反射获取注册类的订阅事件集合
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    //如果获取的事件链List为空,则抛出异常​
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //将注册类与事件链List关联起来,放入缓存​
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
       //返回订阅链list​
        return subscriberMethods;
    }
}
  • 根据注册类class,从缓存中获取订阅事件链​,若获取的订阅信息方法集合已存在,则直接返回该集合。
  • 是否使用通过反射生成的索引,默认false。
  • 若使用反射生成索引 则通过索引类取出注册类的订阅事件信息,否则通过反射获取订阅信息集合
  • 将注册类与事件链List关联起来,放入缓存​
[2.2] 在 函数 subscribe(subscriber, subscriberMethod) 中发布订阅事件
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class eventType = subscriberMethod.eventType;   //获取事件的类型
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);  //生成订阅实例
    CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);  //根据事件类型,从Map缓存中查找订阅链表
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);  //保存入Map中
    } else {
        if (subscriptions.contains(newSubscription)) {  //在订阅链表中匹配当前订阅事件
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType); //抛出一个EventBusException异常,告诉用户该事件已经被订阅过了
        }
    }
​    //将当前的订阅事件放入 subscriptions
    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;
        }
    }
   //通过注册类从typesBySubscriber中获取​获取对应的subscribedEvents 
    List> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        //将注册类和注册类中的订阅方法的evenType属性集合关联起来放入​ typesBySubscriber 中
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
   //将订阅方法​的eventType属性放入subscribedEvents集合
    subscribedEvents.add(eventType);
    //判断当前订阅事件方法是不是粘性事件​
    if (subscriberMethod.sticky) {
        if (eventInheritance) {  //判断是不是继承事件
            Set, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry, 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);
        }
    }
}
  • 首先根据传递的订阅信息和完整的订阅方法集合,生成订阅实例
  • 根据事件类型,从subscriptionsByEventType Map缓存中查找对应的订阅链表
  • 若获取的订阅链表为空,则将当前订阅方法的eventType属性与空的CopyOnWriteArrayList关联起来放入subscriptionsByEventTypeMap集合,之后我们会向CopyOnWriteArrayList的实例对象subscribedEvents填充数据,保证他们之间关联准确性
  • 将当前的订阅事件放入 subscriptions
  • 通过注册类从typesBySubscriber 中获取​获取对应的subscribedEvents
  • 将注册类和注册类中的订阅方法的evenType属性集合关联起来放入​ typesBySubscriber
  • 将订阅方法​的eventType属性放入subscribedEvents集合
  • 判断当前订阅事件方法是不是粘性事件​,若是则 从缓存stickyEvents获取,并将其当做一个新事件发布到总线

EventBus注销

EventBus注销流程图 :

EventBus系列『一』——注册与注销_第2张图片
EventBus注销流程图.jpg

与注册相比注销流程就显得简单的多了,主要的就是将事件及注册类的移除,接下来我们结合源码进行了解一下

[1] EventBus.getDefault() 获取EventBus实例

[2] 进入 EventBus.java #方法 EventBus.getDefault().unregister(this)函数

public synchronized void unregister(Object subscriber) {
     //通过注册类从typesBySubscriber中获取对应的订阅事件类型集合
    List> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {​
        for (Class eventType : subscribedTypes) {
            // [ 2.1 ] 移除与当前注册类相同的对象中包含的订阅事件
            unsubscribeByEventType(subscriber, eventType);
        }
         //从​typesBySubscriber中移除注册类
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
  • 通过注册类从typesBySubscriber中获取对应的订阅事件类型集合
  • 遍历集合移除与当前注册类相同的对象中包含的订阅事件
  • 遍历结束后 即代表注册类中的所有的订阅都已经被移除后从​typesBySubscriber中移除注册类
[ 2.1 ] 移除与当前注册类相同的对象中包含的订阅事件
private void unsubscribeByEventType(Object subscriber, Class eventType) {
    //通过eventType从​subscriptionsByEventType获取订阅事件集合
    List subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
             //判定当前订阅事件的注册类是否与要取消的注册类对象相同​
            if (subscription.subscriber == subscriber) {
                 //从订阅集合中移除​
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}
  • 通过eventType​subscriptionsByEventType获取订阅事件集合
  • 遍历集合,判定当前订阅事件的注册类是否与要取消的注册类对象相同,若判定成功则将订阅事件从集合中移除。

总结

本篇我们详细讲解了EventBus注册注销流程,从中可以发现 :

  1. 在注销流程其实没什么复杂操作,就是讲订阅事件和注册类进行了逐一移除.
  2. 注册流程就相对复杂许多,在注册的最后我们发现了对postSticky 粘性事件的特殊处理,而并没有对POST 事件做什么特殊处理。

This ALL! Thanks EveryBody!

请关注下篇

EventBus系列——Post 事件的发布与接收

你可能感兴趣的:(EventBus系列『一』——注册与注销)