EventBus3.x源码分析之注册(一)

EventBus3.x源码分析之注册(一)

官方链接:https://github.com/greenrobot/EventBus
官方图例:
这里写图片描述

主要流程:

发布者通过post发送事件,EventBus将事件传递给提前注册好的订阅类。当然分发和处理事件可以指定不同线程,可以给订阅者设置优先级,可以设置粘性事件。索引方式提高性能等。

EventBus的成员:
EventBus {
    static volatile EventBus defaultInstance;
    //EventBus功能构建器
    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

    //以当前事件类为key,存放事件,包括事件的父类,接口类的集合
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();

    //以订阅方法的参数类型为key,存放订阅类和订阅方法的subscription集合
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

    //以订阅类为key,存储所有的订阅方法参数类型。
    private final Map<Object, List<Class<?>>> typesBySubscriber;

    //以事件class为key,Object为value存入,保存粘性事件(可以实现先发送后注册的事件)
    private final Map<Class<?>, Object> stickyEvents;

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            //以"空间换时间"的方式为每个线程提供一份实例,保证数据安全速度够快:
            return new PostingThreadState();
        }
    };

    private final MainThreadSupport mainThreadSupport;//提供线程,和事件分发器
    private final Poster mainThreadPoster;//主线程事件分发器
    private final BackgroundPoster backgroundPoster;//子线程事件分发器
    private final AsyncPoster asyncPoster;//异步事件分发器
    private final SubscriberMethodFinder subscriberMethodFinder;//可以理解为订阅方法的管理类
    private final ExecutorService executorService;//提供线程池

    //构建者中的一些配置
    private final boolean throwSubscriberException;
    private final boolean logSubscriberExceptions;
    private final boolean logNoSubscriberMessages;
    private final boolean sendSubscriberExceptionEvent;//反射发生异常时,是否发送异常事件
    private final boolean sendNoSubscriberEvent;//如果没有通过事件找到对应的订阅信息,是否发送该事件
    private final boolean eventInheritance;//是否需要发送事件的父类或接口类,默认true

    //通过addIndex添加的事件的索引
    private final int indexCount;
}
1.注册:
EventBus#
public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    //获取所有的订阅方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //循环订阅
            subscribe(subscriber, subscriberMethod);
        }
    }
}

这里是注册入口,通过subscriberMethodFinder.findSubscriberMethods(subscriberClass);会去获取所有的符合条件的订阅方法

SubscriberMethodFinder#
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //是否忽略注册时通过addIndex方法添加的订阅信息(EventBus3.0以后强大的性能优化)
    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 {
        //以订阅类名为key,订阅类中的订阅方法为value存入订阅方法的缓存容器(同步安全效率高的Hasmap,采用分段锁技术)中。
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

1、这个类维护了一个以subscriber(订阅类)为key,subscriberMethods(订阅方法)为value的缓存容器,目的是为了再次注册时直接将该订阅类中的订阅方法返回。
2、ignoreGeneratedIndex:是可以从EventBusBuilder中配置的boolean类型字段,代表是否忽注册通过EventBusBuilder中addIndex方法添加的订阅信息。默认不忽略,会走findUsingInfo。但是这次我们只分析findUsingReflection。
注:这里属于EvetBus新特性利用注解方式编译时生成订阅信息,无需运行时查找订阅类中的方法,主要功能在meta包和annotationprocessor包中,下篇文章会总结~。

SubscriberMethodFinder#
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private FindState prepareFindState() {
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            if (state != null) {
                FIND_STATE_POOL[i] = null;
                return state;
            }
        }
    }
    return new FindState();
}
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //在运行时添加方法
        findUsingReflectionInSingleClass(findState);
        //到父类中继续查找
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

prepareFindState()此方法维护了一个长度为4的findState的对象缓存池。这个findState就是保存了订阅者和订阅信息的封装类。

SubscriberMethodFinder#
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    //获取订阅类中所有符合条件的方法
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
        //这种获取方式不会获取父类的public方法,所以要去父类中查找并添加订阅方法(就要去父类中找就要执行moeToUperclass)
        //但是父类中如果找到了被重写的public方法,还是会走methodClassOld.isAssignableFrom(methodClass)也会返回false防止再添加父类中的订阅方法。
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        //这种获取方式会获取到该类所有的public方法包括父类的public方法,但如果是重写的方法,就会获取重复
        //但是只需要一个订阅方法即可,所以会有methodClassOld.isAssignableFrom(methodClass)也会返回false防止再添加父类中的订阅方法。
        //但是在下一次moveToSuperclass方法中,相当于又在遍历父类,所以需要将tag置为true跳过。
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        //通过修饰符筛选,只支持public,且不支持abstract,static等
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //拿到方法的返回值类型,其实就是事件类型
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {//只接受一个参数的订阅方法
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    //进行方法校验(方法,参数类型)
                    //注:这里的参数类型就是后面发送的事件类型
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        //将符合条件的方法添加到订阅方法列表
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

1、这里的篇幅较长,目的是为了找到findState.class(订阅类)中的订阅方法
2、对满足条件的方法(如需要是public,但不是abstract,static等;只能有一个参数;带有@Sbscribe注解)找到,然后通过findState.checkAdd(method, eventType)进行校验,找寻完毕将订阅方法添加到订阅方法列表中。
注:在查找所有方法时有两种方式getDeclaredMethods()和getMethods(),也是EventBus在完善https://github.com/greenrobot/EventBus/issues/149这个issue的时候提供的解决方案,这里我也给出了比较详细的注释~

FindState#
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
boolean checkAdd(Method method, Class<?> eventType) {
    // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
    // Usually a subscriber doesn't have methods listening to the same event type.
    Object existing = anyMethodByEventType.put(eventType, method);
    //如果anyMethodByEventType没有该eventType类型的key返回true
    if (existing == null) {
        //不存在该参数类型的方法,直接添加到订阅者
        return true;
    } else {
        //存在相同的参数类型,代表参数类型相同,但可能方法名不同,或者方法名也相同(父类中的被重写的方法)
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                // Paranoia check
                throw new IllegalStateException();
            }
            // Put any non-Method object to "consume" the existing Method
            anyMethodByEventType.put(eventType, this);
        }
        return checkAddWithMethodSignature(method, eventType);
    }
}

1、anyMethodByEventType这个map通过参数类型将方法保存,目的是为了排除相同的订阅方法。
2、官方注释这里有两层校验,一层是参数类型校验,另一层是方法签名校验;其实多数订阅者不会出现相同参数类型,不同方法名。
3、第一层校验通过直接返回true(代表没有找到相同的参数类型的方法,代表该方法可被订阅),否则(代表出现了相同的参数类型,不同给的方法名)进行少数的第二层校验checkAddWithMethodSignature((Method) existing, eventType)

FindState#
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    //所以再次校验需要用到方法名&参数类型名来作为签名
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    //找到相同的签名之前添加的方法所在类
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    //找不到说明为参数类型相同,方法名不同;
    //找到了,说明存在了都相同的情况,这种时子父类情况,而methodClassOld.isAssignableFrom(methodClass)也会返回false。
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class             
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

1、subscriberClassByMethodKey这个map的作用同anyMethodByEventType,为了排除相同方法签名的订阅方法。
2、这里利用StringBuilder将方法名&参数类型名作为方法的签名,会出现一个问题就是如果该订阅类存在父类,那么可能出现相同的签名,这种情况需要排除掉父类中的订阅方法;如methodClassOld == null代表不是继承的方法,只有订阅类中存在直接添加即可,但是如果methodClassOld!=null,代表出现了方法签名相同的订阅方法,这个方法就是父类中被重写的那个方法,当然该方法不能被添加。所以通过methodClassOld.isAssignableFrom(methodClass)返回false略过。
注:这里的methodOld返回的一定是子类,因为查找方法从子类开始,所以子类一定不是父类的当前类或父类,所以返回false。

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//在当前类和自定义的父类中找不到订阅方法了为止
while (findState.clazz != null) {
    findState.subscriberInfo = getSubscriberInfo(findState);
    if (findState.subscriberInfo != null) {
        //从通过addIndex方法,添加的订阅信息找到并添加
        SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
        for (SubscriberMethod subscriberMethod : array) {
            if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                findState.subscriberMethods.add(subscriberMethod);
            }
        }
    } else {
        //在运行时添加方法
        findUsingReflectionInSingleClass(findState);
    }
    //到父类中继续查找
    findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}

此时回到findUsingInfo这个方法,走了一圈后说明,
1、如果找类中方法通过getDeclaredMethods找到的就要从父类中继续查找,因为它找不到父类中的public方法;
2、但是如果通过getMethods找到的包括了父类中的public,所以有设置findState.skipSuperClasses = true;

FindState#
void moveToSuperclass() {
    //跳过父类
    if (skipSuperClasses) {
        clazz = null;
    } else {
        //不跳过父类
        clazz = clazz.getSuperclass();
        String clazzName = clazz.getName();
        /** Skip system classes, this just degrades performance. */
        if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
            clazz = null;
        }
    }
}

通过getDeclaredMethods找方法会不断的向上层查找知道找到不是自己定义的(系统的父类)为止.

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    //将findstate清空,并将订阅者方法返回
    findState.recycle();
    System.out.println(subscriberMethods.toString());
    synchronized (FIND_STATE_POOL) {
        //这里做了一个findState的缓存大小为4
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

最后把符合条件的方法都添加到了findState中的findState.subscriberMethods集合中。最后通过getMethodsAndRelease(FindState findState)将订阅方法列表返回。

以上的整个流程就是非粘性事件注册,粘性事件会在后面的总结中说明。
梳理整个流程:
就是通过当前的订阅类找到订阅类中的所有方法,进行筛选,筛选出public 非static非abstract,只有一个参数类型,带有@Subscribe注解的方法,并进行非重订阅校验。验证通过后会返回一个订阅列表然后开始订阅。

1.订阅

就是将订阅方法通过参数类型(事件类型)为key进行封装,等待post事件的时候,将订阅方法取出回调。

1.非粘性订阅
EventBus#
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    //包装了订阅类和订阅方法
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //通过参数类型获取subscription集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //为null说明不存在该参数类型的方法没有被订阅过
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //subscriptions不为null的情况是指方法名不同,但是参数类型相同,同一个key将上次的存储的取出来了。

        //一个类不可被重复订阅,注:这里的contains方法会走到Subscrptions的equals方法,最终比较的是是否存在相同的订阅类名,方法名,参数类型名。
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //如果长度不为0代表有大于1个的不同方法名,相同参数名的方法
    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;
        }
    }

    //以订阅类为key,存储所有的订阅方法参数类型。维护此map是为了进行反注册
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    ...
}

1、subscriptionsByEventType这个map的作用主要就是用于在post事件的时候,根据事件类型,将订阅方法取出回调。
2、typesBySubscriber这个map的作用是用于在反注册时,通过当前订阅类为key,将订阅信息进行反注册。
3、subscriptions这个List用做储存订阅信息。可以通过事件类型从subscriptionsByEventType取出。
注:在进行获取订阅列表进行订阅方法添加的时候可以给SubscriberMethod(订阅方法包装类)设置三个属性ThreadMode threadMode; int priority; boolean sticky;这里用到了其中一个就是priority。
分析代码,如果订阅列表中没有订阅方法,直接添加;但是如果之前有订阅方法,就会将priority大的订阅方法添加到最前面,保证被先处理。

2.粘性订阅

有一种需求是你想要提前发送事件,等一旦注册马上处理该事件。

同样,在进行获取订阅列表进行订阅方法添加的时候可以给SubscriberMethod(订阅方法包装类)设置三个属性ThreadMode threadMode; int priority; boolean sticky;这里用到了其中一个就是sticky。
如果订阅方法支持sticky

EventBus#
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    ...

    //订阅方法是否支持粘性
    if (subscriberMethod.sticky) {
        //是否需要发送事件的父类
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // 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>).

            //这里可以实现先通过#postSticky(Object event)发送事件在进行订阅、
            //而且一旦注册马上事件马上进入队列或处理
            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);
        }
    }
}

普通订阅都是在订阅好以后,postStickey(Object object)发送事件,然后执行回调;
粘性订阅是指利用postStickey(Object object)发送事件:

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

stickyEvents这个map会在发送事件前维护一个粘性事件列表,在刚订阅的时候直接发送事件,就行处理。

2.反注册
EventBus#
public synchronized void unregister(Object subscriber) {
    //通过当前订阅类找出之前保存的额eventTypes集合
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            //进行反注册
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            //看subscriptionsByEventType里面的subsciption中是否存在当前的subscriber
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

订阅时已经通过订阅类维护了一个订阅方法类型的列表,通过这个列表中的元素可以在subscriptionsByEventType找到对应的订阅信息,并进行反注册。

以上就是EventBus的注册流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值